From cabccb7dbe760c8adaa8521c2a65cf5663130c65 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 18 Mar 2022 13:10:32 -0700 Subject: [PATCH 01/41] [Deep Merge Order] Create a proposal (#3271) --- proposal/deep-merge-order.md | 123 +++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 proposal/deep-merge-order.md diff --git a/proposal/deep-merge-order.md b/proposal/deep-merge-order.md new file mode 100644 index 0000000000..406fd07c23 --- /dev/null +++ b/proposal/deep-merge-order.md @@ -0,0 +1,123 @@ +# Deep Merge Order: Draft 1 + +*([Issue](https://github.com/sass/sass/issues/3092))* + +This proposal changes the ordering of maps returned by `map.deep-merge()` to +match that returned by `map.merge()`. + +## Table of Contents + +* [Background](#background) +* [Summary](#summary) + * [Design Decisions](#design-decisions) + * [Whether to Specify Order](#whether-to-specify-order) +* [Functions](#functions) + * [`map.deep-merge()`](#mapdeep-merge) +* [Deprecation Process](#deprecation-process) + +## Background + +> This section is non-normative. + +When `map.deep-merge()` was first discussed in [issue 1739] and later [added to +the spec], their ordering wasn't explicitly discussed. In practice, the ordering +implied by the original specification put any keys that appeared in both maps at +the end of the result, in the order they appeared in `$map2`. This was different +than the ordering produced by the `map.merge()` function in a way that confused +users. + +[issue 1739]: https://github.com/sass/sass/issues/1739 +[added to the spec]: ../accepted/nested-map-functions.md + +## Summary + +> This section is non-normative. + +This proposal changes the `map.deep-merge()` function to match the ordering of +`map.merge()`, in which all keys in `$map1` appear in the result the same order +they did in `$map1` (whether or not they're in `$map2`), followed by all keys +that are only in `$map2` in the same relative order as in `$map2`. For example: + +* `map.deep-merge((a: 1, b: 1), (b: 2, c: 2))` produces `(a: 1, b: 2, c: 2)` in + both the current spec and this proposal. + +* `map.deep-merge((a: 1, b: 1), (a: 2, c: 2))` produces `(b: 1, a: 2, c: 2)` in + the current spec but `(a: 2, b: 1, c: 2)` in this proposal. + +### Design Decisions + +#### Whether to Specify Order + +Rather than change the specified order of map entries, we considered updating +the specification to explicitly make the order an implementation detail. This +would have the advantage of allowing implementations to choose a more performant +ordering in the future if, for example, they used an immutable representation of +maps that could re-use internal data structures. + +However, because in practice there's currently only one recommended +implementation of Sass, its behavior would still end up being the *de facto* +standard. In addition, users clearly desire an intuitive map ordering and +there's not clear evidence that any performance gains would be substantial +enough to warrant breaking that intuition. + +## Functions + +Replace the definition of the `deep-merge()` function in the `sass:map` built-in +module with the following definition: + +### `map.deep-merge()` + +``` +deep-merge($map1, $map2) +``` + +* If `$map1` and `$map2` are not maps, throw an error. + +* Let `merged` be an empty map. + +* For each `old-key`/`old-value` pair in `$map1`: + + * If `$map2` has a key `new-key` that's `==` to `old-key`: + + * Let `new-value` be the value associated with `new-key` in `$map2`. + + * If both `old-value` and `new-value` are maps, set `new-value` to the + result of calling `deep-merge()` with `old-value` and `new-value`. + + * Associate `old-key` with `new-value` in `merged`. + + * Otherwise, associate `old-key` with `old-value` in `merged`. + +* For each `new-key`/`new-value` pair in `$map2`: + + * If `merged` doesn't have key that's `==` to `new-key`, associate `new-key` + with `new-value` in `merged`. + +* Return `merged`. + +> Note that the order of keys in each merged map is the same as the keys in +> `$map1`, with any new keys from `$map2` added at the end in the same order +> they appear in `$map2`. This matches the ordering of the `merge()` function. + +## Deprecation Process + +This is technically a breaking change, since stylesheets could be relying on the +current ordering of `map.deep-merge()`. However, there are several reasons why a +standard deprecation process isn't a good fit here: + +* There isn't a good way to deprecate the old behavior non-disruptively. If we + support the old behavior as written, the new behavior would need to be awkward + to use. + +* The breaking change is small. Output ordering is not a core part of + `map.deep-merge()`'s behavior and is unlikely to be something anyone is + relying on in practice. + +* `map.deep-merge()` is relatively young, which means that there are not as many + Sass stylesheets using it (and thus relying on every aspect of its behavior) + as there are using older behaviors. + +* The change is a clear improvement in terms of consistency with `map.merge()` + and with merge behavior in other languages. It could even be argued as a bug + fix. Any pain caused by this change is likely to be mitigated by the pain due + to confusing ordering it will prevent. From ac8d416fe24c734093787b6dfd16d1da593fae03 Mon Sep 17 00:00:00 2001 From: Myles Lewando <49149591+codemacabre@users.noreply.github.com> Date: Thu, 24 Mar 2022 22:48:30 +0000 Subject: [PATCH 02/41] =?UTF-8?q?=F0=9F=93=9D=20Require=20correct=20url=20?= =?UTF-8?q?module=20in=20FileImporter=20doc=20(#3268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js-api-doc/importer.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js-api-doc/importer.d.ts b/js-api-doc/importer.d.ts index 68a834023f..124425a0e7 100644 --- a/js-api-doc/importer.d.ts +++ b/js-api-doc/importer.d.ts @@ -23,7 +23,7 @@ import {PromiseOr} from './util/promise_or'; * @example * * ```js - * const {fileURLToPath} = require('url'); + * const {pathToFileURL} = require('url'); * * sass.compile('style.scss', { * importers: [{ From e528c816d2c14148013645682d006fbff21fd55a Mon Sep 17 00:00:00 2001 From: Jennifer Thakar Date: Wed, 30 Mar 2022 15:14:44 -0700 Subject: [PATCH 03/41] Don't simplify calcs in supports declarations (#3272) This is a fast-track proposal. Closes #3259. --- spec/types/calculation.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/types/calculation.md b/spec/types/calculation.md index 21fe44acd0..ef95681b4b 100644 --- a/spec/types/calculation.md +++ b/spec/types/calculation.md @@ -105,7 +105,7 @@ type CalculationValue = | CalculationInterpolation | CalculationOperation | Calculation; - + interface CalculationInterpolation { value: string; } @@ -189,6 +189,9 @@ This algorithm takes a calculation `calc` and returns a number or a calculation. > This algorithm is intended to return a value that's CSS-semantically identical > to the input. +* If `calc` was parsed from an expression within a `SupportsDeclaration`'s + `Expression`, but outside any interpolation, return a `calc` as-is. + * Let `arguments` be the result of [simplifying](#simplifying-a-calculationvalue) each of `calc`'s arguments. From b1cd040fedeafc8a1dea21d73de45404b5e9ac69 Mon Sep 17 00:00:00 2001 From: Jennifer Thakar Date: Wed, 6 Apr 2022 19:19:51 -0700 Subject: [PATCH 04/41] Add support for :where (#3283) Fixes #3130. In addition to updating the procedure for extending a simple selector to treat `:where` the same as `:is` and `:matches`, this also modifies the second law of extend to account for specificity being modified by pseudoselectors like `:where`. This is a fast-track proposal. --- spec/at-rules/extend.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/spec/at-rules/extend.md b/spec/at-rules/extend.md index a40338e199..88a7bcdb5c 100644 --- a/spec/at-rules/extend.md +++ b/spec/at-rules/extend.md @@ -203,7 +203,7 @@ that includes CSS for *all* modules transitively used or forwarded by > Because this traverses modules depth-first, it emits CSS in reverse > topological order. - + * Let `initial-imports` be the longest initial subsequence of top-level statements in `domestic`'s CSS tree that contains only comments and `@import` rules *and* that ends with an `@import` rule. @@ -247,7 +247,7 @@ a selector list `extender` and returns a selector list. * If it's a combinator, add it to each selector in `options`. * For each simple selector `simple` in `compound`: - + * Let `new-list` be the result of [extending](#extending-a-simple-selector) `simple` with `target` and `extender`. @@ -289,7 +289,7 @@ and a selector list `extender` and returns a selector list. * Let `extended-arg` be `extend(arg, target, extender)`. * If `extendee`'s [unprefixed] name is `not`: - + * If `arg` has no complex selectors with more than one compound selector, remove all complex selectors with more than one compound selector from `extended-arg`. @@ -301,8 +301,8 @@ and a selector list `extender` and returns a selector list. * If any complex selectors in `extended-arg` contain only a single compound selector which in turn contains a single pseudo selector with a selector argument, remove them from `extended-arg`. If any of the removed selectors - were pseudo-selectors named `is` or `matches`, add their selector - arguments to `extended-arg`. + were pseudo-selectors named `is`, `where`, or `matches`, add their + selector arguments to `extended-arg`. > For example, `:not(:is(a, b))` becomes `:not(a, b)`. @@ -439,8 +439,12 @@ For example, `extend(:not(.foo), .foo, .bar)` should produce The second law of extend says that the specificity of a new selector to match a given extender must be greater than or equal to the specificity of that -extender. For example, `extend(a, a, a.foo)` should produce `a, a.foo` even +extender when modified in the same way as the target is modified within the +extendee. For example, `extend(a, a, a.foo)` should produce `a, a.foo` even though (again) `a.foo` matches a subset of elements matched by `a`. +`extend(:where(.x), .x, .x .y)` should produce `:where(.x, .x .y)` even though +it has lower specificity than `.x .y`, because `:where` eliminates the +specificity of both `.x` and `.x .y`. This still leaves room for optimizations. For example, `extend(.bar a, a, a.foo)` can just produce `.bar a` (omitting `.bar a.foo`). From 12cee2dab996a5424160e644ff56f040a42bc0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Mon, 11 Apr 2022 17:09:26 -0700 Subject: [PATCH 05/41] Fix incorrect documentation for url in StringOptionsWithoutImporter (#3278) --- js-api-doc/options.d.ts | 12 +++++++----- spec/spec.md | 11 ++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts index 1f3ba66358..227243750c 100644 --- a/js-api-doc/options.d.ts +++ b/js-api-doc/options.d.ts @@ -333,8 +333,8 @@ export interface Options { * Options that can be passed to [[compileString]] or [[compileStringAsync]]. * * If the [[StringOptionsWithImporter.importer]] field isn't passed, the - * entrypoint file can't load files relative to itself and the [[url]] field is - * optional. + * entrypoint file can load files relative to itself if a `file://` URL is + * passed to the [[url]] field. * * @typeParam sync - This lets the TypeScript checker verify that asynchronous * [[Importer]]s, [[FileImporter]]s, and [[CustomFunction]]s aren't passed to @@ -354,9 +354,11 @@ export interface StringOptionsWithoutImporter syntax?: Syntax; /** - * The canonical URL of the entrypoint stylesheet. If this isn't passed along - * with [[StringOptionsWithoutImporter.importer]], it's optional and only used - * for error reporting. + * The canonical URL of the entrypoint stylesheet. + * + * A relative load's URL is first resolved relative to [[url]], then resolved + * to a file on disk if it's a `file://` URL. If it can't be resolved to a + * file on disk, it's then passed to [[importers]] and [[loadPaths]]. * * @category Input */ diff --git a/spec/spec.md b/spec/spec.md index 63c6f3ffe2..55240843f9 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -133,7 +133,16 @@ It runs as follows: > value, implementations should help users understand the source of the string > if possible. -* If `importer` is null, set it to a function that always returns null. +* If `importer` is null: + + * If `url` is a `file:` URL, set `importer` to be a [filesystem importer] with an + arbitrary `base`. + + > This importer will only ever be passed absolute URLs, so its base won't + > matter. + + * If `url` is not a `file:` URL, set `importer` to be a function that always + returns null. * Let `file` be the [source file][] with `ast`, canonical URL `url`, and importer `importer`. From de325aae5f3514bf660cb313ee026e748707a6b6 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 18 Apr 2022 16:26:55 -0700 Subject: [PATCH 06/41] Exempt Twitter links from the link check test (#3292) These have recently begun failing and blocking our CI. --- test/link-check.ts | 2 ++ tool/types/markdown-link-check.d.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/test/link-check.ts b/test/link-check.ts index 57a00197b4..af4eb37b82 100644 --- a/test/link-check.ts +++ b/test/link-check.ts @@ -73,6 +73,8 @@ function runLinkCheck( baseUrl: '', // If Github rate limit is reached, wait 60s and try again. retryOn429: true, + // Twitter links consistently fail. + ignorePatterns: [{pattern: /^https?:\/\/twitter\.com(\/|$)/}], }, (error, results) => { if (error) { diff --git a/tool/types/markdown-link-check.d.ts b/tool/types/markdown-link-check.d.ts index 5dca8735ba..1ea916361d 100644 --- a/tool/types/markdown-link-check.d.ts +++ b/tool/types/markdown-link-check.d.ts @@ -3,6 +3,7 @@ declare module 'markdown-link-check' { export interface Options { baseUrl?: string; retryOn429?: boolean; + ignorePatterns: Array<{pattern: RegExp}>; } export interface Result { From c1cb05754916b70e00250bfae4ee1d15fa33114b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 18 Apr 2022 17:47:45 -0700 Subject: [PATCH 07/41] Add an MIT license (#3286) All existing contributors have authorized releasing their contributions under this license. Closes #3254 --- LICENSE | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..01411cde00 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2022, Google Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 8b5c8cf42e2bc6b2c733c4eb363c4311b15e0018 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 22 Apr 2022 17:27:23 -0700 Subject: [PATCH 08/41] Mark the deep-merge-order spec as accepted (#3293) --- {proposal => accepted}/deep-merge-order.md | 0 spec/built-in-modules/map.md | 23 +++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) rename {proposal => accepted}/deep-merge-order.md (100%) diff --git a/proposal/deep-merge-order.md b/accepted/deep-merge-order.md similarity index 100% rename from proposal/deep-merge-order.md rename to accepted/deep-merge-order.md diff --git a/spec/built-in-modules/map.md b/spec/built-in-modules/map.md index bfa70fc47e..20f3b0a220 100644 --- a/spec/built-in-modules/map.md +++ b/spec/built-in-modules/map.md @@ -25,23 +25,32 @@ deep-merge($map1, $map2) * If `$map1` and `$map2` are not maps, throw an error. -* Let `merged` be a copy of `$map1`. +* Let `merged` be an empty map. -* For each `new-key`/`new-value` pair in `$map2`: - - * If `merged` has a key `old-key` that's `==` to `new-key`: +* For each `old-key`/`old-value` pair in `$map1`: - * Let `old-value` be the value associated with `old-key` in `merged`. + * If `$map2` has a key `new-key` that's `==` to `old-key`: - * Remove `old-key`/`old-value` from `merged`. + * Let `new-value` be the value associated with `new-key` in `$map2`. * If both `old-value` and `new-value` are maps, set `new-value` to the result of calling `deep-merge()` with `old-value` and `new-value`. - * Associate `new-key` with `new-value` in `merged`. + * Associate `old-key` with `new-value` in `merged`. + + * Otherwise, associate `old-key` with `old-value` in `merged`. + +* For each `new-key`/`new-value` pair in `$map2`: + + * If `merged` doesn't have key that's `==` to `new-key`, associate `new-key` + with `new-value` in `merged`. * Return `merged`. +> Note that the order of keys in each merged map is the same as the keys in +> `$map1`, with any new keys from `$map2` added at the end in the same order +> they appear in `$map2`. This matches the ordering of the `merge()` function. + ### `deep-remove()` ``` From 066d10831d9d93c5fdfed8a196ad3a316c894812 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 25 Apr 2022 22:58:47 +0200 Subject: [PATCH 09/41] Fix the algorithm computing the default use namespace (#3289) Co-authored-by: Jennifer Thakar --- spec/at-rules/use.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/at-rules/use.md b/spec/at-rules/use.md index bd65bdc880..ca1713e113 100644 --- a/spec/at-rules/use.md +++ b/spec/at-rules/use.md @@ -95,8 +95,8 @@ This algorithm takes a `@use` rule `rule`, and returns either an identifier or * Let `basename` be the text after the final `/` in `path`, or the entire `path` if `path` doesn't contain `/`. -* Let `module-name` be the text before the first `.` in `path`, or the entire - `path` if `path` doesn't contain `.`. +* Let `module-name` be the text before the first `.` in `basename`, or the entire + `basename` if `basename` doesn't contain `.`. * If `module-name` begins with `_`, remove the leading `_` and set `module-name` to the result. From 012f79f3738a33b6c63a9df07ef44c1cccdf5937 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 26 Apr 2022 21:34:58 +0200 Subject: [PATCH 10/41] Add the syntax definition for the import rule (#3294) --- spec/at-rules/import.md | 22 ++++++++++++++++++++-- spec/syntax.md | 12 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/spec/at-rules/import.md b/spec/at-rules/import.md index b67ab577c3..2fd672b913 100644 --- a/spec/at-rules/import.md +++ b/spec/at-rules/import.md @@ -6,6 +6,23 @@ still supported for backwards-compatibility. [`@use` rule]: use.md +## Table of Contents + +* [Syntax](#syntax) +* [Semantics](#semantics) + +## Syntax + +
+**ImportRule**                ::= '@import' ImportArgument (',' ImportArgument)*
+**ImportArgument**            ::= ImportUrl ImportSupportsDeclaration? [MediaQueryList][]?
+**ImportUrl**                 ::= QuotedString | [InterpolatedUrl][]
+**ImportSupportsDeclaration** ::= 'supports(' SupportsDeclaration ')'
+
+ +[InterpolatedUrl]: ../syntax.md#InterpolatedUrl +[MediaQueryList]: media.md#syntax + ## Semantics To execute an `@import` rule `rule`: @@ -16,8 +33,9 @@ To execute an `@import` rule `rule`: * `argument`'s URL string begins with `http://` or `https://`. * `argument`'s URL string ends with `.css`. - * `argument`'s URL string is syntactically defined as a `url()`. - * `argument` has a media query and/or a supports query. + * `argument`'s URL is an `InterpolatedUrl`. + * `argument` has an `ImportSupportsDeclaration`. + * `argument` has a `MediaQueryList`. > Note that this means that imports that explicitly end with `.css` are > treated as plain CSS `@import` rules, rather than importing stylesheets as diff --git a/spec/syntax.md b/spec/syntax.md index df9b5d232f..d162633df1 100644 --- a/spec/syntax.md +++ b/spec/syntax.md @@ -7,6 +7,7 @@ * [Vendor Prefix](#vendor-prefix) * [Grammar](#grammar) * [`InterpolatedIdentifier`](#interpolatedidentifier) + * [`InterpolatedUrl`](#interpolatedurl) * [`Name`](#name) * [`SpecialFunctionExpression`](#specialfunctionexpression) * [`PseudoSelector`](#pseudoselector) @@ -50,6 +51,17 @@ as the *unprefixed identifier*. No whitespace is allowed between components of an `InterpolatedIdentifier`. +### `InterpolatedUrl` + +
+**InterpolatedUrl**         ::= 'url(' (QuotedString | InterpolatedUnquotedUrlContents) ')'
+**InterpolatedUnquotedUrlContents** ::= ([unescaped url contents][] | [escape][] | Interpolation)*
+
+ +[unescaped url contents]: https://www.w3.org/TR/css-syntax-3/#url-token-diagram + +No whitespace is allowed between components of an `InterpolatedUnquotedUrlContents`. + ### `Name`

From 8cbe77da7ccf4ff81d7ab4d72eb925fce0c52ad7 Mon Sep 17 00:00:00 2001
From: Natalie Weizenbaum 
Date: Mon, 2 May 2022 14:42:09 -0700
Subject: [PATCH 11/41] Fix a bug in the custom function example (#3305)

Closes #3304
---
 js-api-doc/options.d.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts
index 227243750c..6d09256a42 100644
--- a/js-api-doc/options.d.ts
+++ b/js-api-doc/options.d.ts
@@ -38,7 +38,8 @@ export type OutputStyle = 'expanded' | 'compressed';
  * const result = sass.compile('style.scss', {
  *   functions: {
  *     "sum($arg1, $arg2)": (args) => {
- *       const value1 = args[0].assertNumber('arg1').value;
+ *       const arg1 = args[0].assertNumber('arg1');
+ *       const value1 = arg1.value;
  *       const value2 = args[1].assertNumber('arg2')
  *           .convertValueToMatch(arg1, 'arg2', 'arg1');
  *       return new sass.SassNumber(value1 + value2).coerceToMatch(arg1);

From 4634461cd89441fbadf5cb7e66380017c801131b Mon Sep 17 00:00:00 2001
From: Jennifer Thakar 
Date: Wed, 11 May 2022 14:20:19 -0700
Subject: [PATCH 12/41] Update hsl/hwb specs to reflex current CSS logic
 (#3311)

Fixes #3298.
---
 accepted/color-4-hwb.md        | 14 +++++++-------
 spec/built-in-modules/color.md |  4 +---
 spec/functions.md              |  6 ++----
 3 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/accepted/color-4-hwb.md b/accepted/color-4-hwb.md
index f2a563a59a..1a34e0060d 100644
--- a/accepted/color-4-hwb.md
+++ b/accepted/color-4-hwb.md
@@ -78,7 +78,7 @@ are left to a future proposal.
 
 ## Procedures
 
-### Scaling a Number 
+### Scaling a Number
 
 This algorithm takes a number `number`, a value `factor`, and a number `max`.
 It's written "scale `` by `` with a `max` of ``". It
@@ -112,7 +112,7 @@ All new functions are part of the `sass:color` built-in module.
   * If either of `$whiteness` or `$blackness` don't have unit `%` or aren't
     between `0%` and `100%` (inclusive), throw an error.
 
-  * Let `hue` be `($hue % 360) / 60` without units.
+  * Let `hue` be `$hue` without units.
 
   * Let `whiteness` be `$whiteness / 100%`.
 
@@ -131,7 +131,7 @@ All new functions are part of the `sass:color` built-in module.
     and rounded to the nearest integers.
 
   * Let `alpha` be the result of [percent-converting][] `$alpha` with a `max` of 1.
-  
+
   * Return a color with the given `red`, `green`, `blue`, and `alpha` channels.
 
   [percent-converting]: ../spec/built-in-modules/color.md#percent-converting-a-number
@@ -260,7 +260,7 @@ This function's new definition is as follows:
 
   * Let `hue`, `saturation`, and `lightness` be the result of calling
     `hue($color)`, `saturation($color)`, and `lightness($color)` respectively.
-  
+
   * If `$hue` isn't null, set `hue` to `hue + $hue`.
 
   * If `$saturation` isn't null, set `saturation` to `saturation + $saturation`
@@ -279,7 +279,7 @@ This function's new definition is as follows:
 
   * Let `hue`, `whiteness`, and `blackness` be the result of calling
     `hue($color)`, `whiteness($color)`, and `blackness($color)` respectively.
-  
+
   * If `$hue` isn't null, set `hue` to `hue + $hue`.
 
   * If `$whiteness` isn't null, set `whiteness` to `whiteness + $whiteness`
@@ -334,7 +334,7 @@ This function's new definition is as follows:
 
   * Let `green` be `$color`'s green channel if `$green` is null or `$green`
     without units otherwise.
-  
+
   * Let `blue` be `$color`'s blue channel if `$blue` is null or `$blue` without
     units otherwise.
 
@@ -401,7 +401,7 @@ This function's new definition is as follows:
 
 * If `$alpha` isn't null, set `alpha` to the result of [scaling][] `alpha` by
   `$alpha` with `max` 1.
-  
+
   [scaling]: #scaling-a-number
 
 * If any of `$red`, `$green`, or `$blue` aren't null:
diff --git a/spec/built-in-modules/color.md b/spec/built-in-modules/color.md
index fca0dd17d8..7bf966afee 100644
--- a/spec/built-in-modules/color.md
+++ b/spec/built-in-modules/color.md
@@ -38,7 +38,7 @@ This built-in module is available from the URL `sass:color`.
 
 ## Procedures
 
-### Percent-Converting a Number 
+### Percent-Converting a Number
 
 This algorithm takes a SassScript number `number` and a number `max`. It returns
 a number between 0 and `max`.
@@ -402,8 +402,6 @@ This function is also available as a global function named `hue()`.
 
   * Let `hue` be the result of [converting] `$hue` to `deg` allowing unitless.
 
-  * Set `hue` to `(hue % 360deg) / 60deg`.
-
   * If either of `$whiteness` or `$blackness` don't have unit `%` or aren't
     between `0%` and `100%` (inclusive), throw an error.
 
diff --git a/spec/functions.md b/spec/functions.md
index 04e07baef6..a70d6ed21b 100644
--- a/spec/functions.md
+++ b/spec/functions.md
@@ -201,7 +201,7 @@ plain CSS function named `"rgb"` that function is named `"rgba"` instead.
 * ```
   rgb($channels)
   ```
-  
+
   * If `$channels` is a [special variable string][], return a plain CSS function
     string with the name `"rgb"` and the argument `$channels`.
 
@@ -286,8 +286,6 @@ plain CSS function named `"hsl"` that function is named `"hsla"` instead.
 
   * Let `hue` be the result of [converting] `$hue` to `deg` allowing unitless.
 
-  * Set `hue` to `(hue % 360deg) / 60deg`.
-
   * If `$saturation` and `$lightness` don't have unit `%`, throw an error.
 
   * Let `saturation` and `lightness` be the result of clamping `$saturation` and
@@ -325,7 +323,7 @@ plain CSS function named `"hsl"` that function is named `"hsla"` instead.
     function string with the name `"hsl"` and the same arguments.
 
   * Otherwise, throw an error.
-  
+
 * ```
   hsl($channels)
   ```

From ac8fedffdcaf1f5ba3ac4ea04966fc1f34c6800c Mon Sep 17 00:00:00 2001
From: Christophe Coevoet 
Date: Fri, 20 May 2022 01:53:34 +0200
Subject: [PATCH 13/41] Update specs to allow CSS imports with arbitrary
 modifiers (#3290)

Closes #3285

Co-authored-by: Natalie Weizenbaum 
---
 spec/at-rules/import.md | 84 ++++++++++++++++++++++++++++++++++++-----
 spec/at-rules/media.md  | 19 +++++-----
 spec/syntax.md          |  4 +-
 3 files changed, 86 insertions(+), 21 deletions(-)

diff --git a/spec/at-rules/import.md b/spec/at-rules/import.md
index 2fd672b913..b77495aab5 100644
--- a/spec/at-rules/import.md
+++ b/spec/at-rules/import.md
@@ -14,28 +14,69 @@ still supported for backwards-compatibility.
 ## Syntax
 
 
-**ImportRule**                ::= '@import' ImportArgument (',' ImportArgument)*
-**ImportArgument**            ::= ImportUrl ImportSupportsDeclaration? [MediaQueryList][]?
-**ImportUrl**                 ::= QuotedString | [InterpolatedUrl][]
-**ImportSupportsDeclaration** ::= 'supports(' SupportsDeclaration ')'
+**ImportRule**            ::= '@import' (ImportArgumentNoMedia ',')* ImportArgument
+**ImportArgumentNoMedia** ::= ImportUrl ImportModifierNoMedia*
+**ImportArgument**        ::= ImportUrl ImportModifier
+**ImportModifierNoMedia** ::= InterpolatedIdentifier* (ImportFunction | ImportSupports)
+**ImportModifier**        ::= ImportModifierNoMedia* InterpolatedIdentifier* ImportMedia?
+**ImportMedia**           ::= [MediaFeatureInParens] (',' [MediaQueryList])*
+                        | InterpolatedIdentifier (',' [MediaQueryList])*
+**ImportSupports**        ::= 'supports(' SupportsDeclaration ')'
+**ImportFunction**        ::= [InterpolatedIdentifier]¹ '(' InterpolatedDeclarationValue? ')'
+**ImportUrl**             ::= QuotedString | [InterpolatedUrl][]
 
+[InterpolatedIdentifier]: ../syntax.md#InterpolatedIdentifier [InterpolatedUrl]: ../syntax.md#InterpolatedUrl +[MediaFeatureInParens]: media.md#syntax [MediaQueryList]: media.md#syntax +1: This identifier may not be `"supports"` or `"and"`. No whitespace is allowed + between it and the following `(`. + +> This somewhat involved grammar was chosen over the simpler +> +> ``` +> ImportRule ::= '@import" (ImportArgument ',')* ImportArgument +> ImportArgument ::= ImportUrl ImportModifier* +> ImportArgument ::= ImportUrl ImportModifier* +> ImportModifier ::= ImportFunction | ImportSupports | MediaQueryList +> ``` +> +> because this simpler version produces a number of problematic ambiguities. For +> example: +> +> * `@import "..." a b(c)` could be parsed as either: +> * `MediaQuery "a", ImportFunction "b(c)"` +> * `MediaQuery "a b", MediaQuery "(c)"` +> * `@import "..." a and(b)` could be parsed as either: +> * `MediaQuery "a", ImportFunction "and(b)"` +> * `MediaQuery "a and(b)"` +> +> To resolve these, this grammar explicitly indicates that a `MediaQueryList` +> and its associated commas may only appear at the end of an `ImportRule`, and +> delineates the exact circumstances in which an `InterpolatedIdentifier` is or +> is not part of a `MediaQueryList`. +> +> Note that this parses `@import "..." layer (max-width: 600px)` differently +> than the CSS standard: in CSS, `layer` is a CSS layering keyword but Sass +> parses it as part of a media query in this instance. This doesn't pose a +> problem in practice because Sass's semantics never depend on how import +> modifiers are parsed. + ## Semantics To execute an `@import` rule `rule`: -* For each of `rule`'s arguments `argument`: +* For each of `rule`'s `ImportArgumentNoMedia`s and `ImportArgument`s `argument`: * If any of the following are true, `argument` is considered "plain CSS": * `argument`'s URL string begins with `http://` or `https://`. * `argument`'s URL string ends with `.css`. * `argument`'s URL is an `InterpolatedUrl`. - * `argument` has an `ImportSupportsDeclaration`. - * `argument` has a `MediaQueryList`. + * `argument` has at least one `ImportModifierNoMedia`. + * `argument` has a non-empty `ImportModifier`. > Note that this means that imports that explicitly end with `.css` are > treated as plain CSS `@import` rules, rather than importing stylesheets as @@ -43,10 +84,33 @@ To execute an `@import` rule `rule`: * If `argument` is "plain CSS": - * Evaluate any interpolation it contains. + * Evaluate each of the following within `argument`'s + `ImportModifierNoMedia`s or `ImportModifier`s, and concatenate the results + into a single string with `" "` between each one: + + * For an `InterpolatedIdentifier` outside an `ImportMedia`, concatenate + the result of evaluating it. + + * For an `ImportFunction`, concatenate: + * The result of evaluating its `InterpolatedIdentifier` + * `"("` + * The result of evaluating its `InterpolatedDeclarationValue` (or `""` + if it doesn't have one) + * `")"` + + * For an `ImportSupports`, concatenate: + * `"supports("` + * The result of evaluating its `SupportsDeclaration` as a CSS string + * `")" + + * For an `ImportMedia`, concatenate the result of evaluating it as a + [`MediaQueryList`] as a CSS string. + + > `ImportMedia` is a subset of the valid syntax of `MediaQueryList`, so + > this will always work. - * Add an `@import` with the evaluated string, media query, and/or supports - query to [the current module][]'s CSS AST. + * Add an `@import` with the evaluated modifiers to [the current module]'s + CSS AST. * Otherwise, let `file` be the result of [loading the file][] with `argument`'s URL string. If this returns null, throw an error. diff --git a/spec/at-rules/media.md b/spec/at-rules/media.md index 1b9b9b5728..de550d5579 100644 --- a/spec/at-rules/media.md +++ b/spec/at-rules/media.md @@ -22,15 +22,16 @@ plain CSS. Media queries are parsed from Sass source using the following syntax:
-**MediaQueryList** ::= MediaQuery (',' MediaQuery)*
-**MediaQuery**     ::= MediaType | (MediaType 'and')? MediaFeature ('and' MediaFeature)*
-**MediaType**      ::= [InterpolatedIdentifier][] [InterpolatedIdentifier][]¹?
-**MediaFeature**   ::= Interpolation
-                  | '(' Expression² ')'
-                  | '(' Expression² ':' Expression ')'
-                  | '(' Expression² [\][] Expression² ')'
-                  | '(' Expression² [\][] Expression² [\][] Expression² ')'
-                  | '(' Expression² [\][] Expression² [\][] Expression² ')'
+**MediaQueryList**       ::= MediaQuery (',' MediaQuery)*
+**MediaQuery**           ::= MediaType ('and' MediaFeature)*
+                       | MediaFeatureInParens ('and' MediaFeature)*
+**MediaType**            ::= [InterpolatedIdentifier][] [InterpolatedIdentifier][]¹?
+**MediaFeature**         ::= Interpolation | MediaFeatureInParens
+**MediaFeatureInParens** ::= '(' Expression² ')'
+                       | '(' Expression² ':' Expression ')'
+                       | '(' Expression² [\][] Expression² ')'
+                       | '(' Expression² [\][] Expression² [\][] Expression² ')'
+                       | '(' Expression² [\][] Expression² [\][] Expression² ')'
 
[InterpolatedIdentifier]: ../syntax.md#interpolatedidentifier diff --git a/spec/syntax.md b/spec/syntax.md index d162633df1..bc53695e1d 100644 --- a/spec/syntax.md +++ b/spec/syntax.md @@ -179,8 +179,8 @@ modifications. The following productions should produce errors: * `@warn` * `@while` -* An `@import` that contains interpolation in the `url()`, the media query, or - the supports query. +* An `@import` that contains interpolation in the `url()` or any of its + `ImportModifier`s. * An `@import` that appears within a style rule or at-rule. From 2a0c99ba9744ae8f2849d0928f95546252ad4d2b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 15 Jun 2022 15:07:56 -0700 Subject: [PATCH 14/41] [Strict Unary] Add a proposal (#3334) --- proposal/strict-unary.md | 80 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 proposal/strict-unary.md diff --git a/proposal/strict-unary.md b/proposal/strict-unary.md new file mode 100644 index 0000000000..de5c7e2995 --- /dev/null +++ b/proposal/strict-unary.md @@ -0,0 +1,80 @@ +# Strict Unary Operators: Draft 1 + +*([Issue](https://github.com/sass/sass/issues/1912))* + +This proposal forbids the syntax `$a -$b`, which surprises many users by parsing +equivalently to `$a - $b` instead of `$a (-$b)`. It does the same for `+`, which +also has a unary form. + +## Table of Contents + +* [Background](#background) +* [Summary](#summary) +* [Alternatives Considered](#alternatives-considered) + * [Spaces on Both Sides](#spaces-on-both-sides) +* [Syntax](#syntax) +* [Deprecation Process](#deprecation-process) + +## Background + +> This section is non-normative. + +Sass is in an awkward syntactic position. On one hand, it's beholden to CSS's +syntax, including the frequent use of space-separated lists of values. On the +other, it wants to provide mathematical operations in a naturalistic way that +matches user expectations from other programming languages and from everyday +mathematical notation. + +This is a particular problem when dealing with operators like `-` and `+` that +can be both *binary*, appearing between two operands like `$a - $b` or `$a + +$b`; or *unary*, appearing before a single operand like `-$a` or `-$b`. In most +programming languages it's possible to parse both of these unambiguously with +any combination of whitespace, but in Sass a construct like `$a -$b` could be +reasonably parsed as either the binary operation `$a - $b` or the unary +operation `$a (-$b)`. + +In practice, we chose to parse it as the binary operation under the logic that +whitespace shouldn't affect the parsing of operators. This logic is sound in +isolation, but in practice it produces surprising and unpleasant behavior for +users. + +## Summary + +> This section is non-normative. + +We will address the confusion by the ambiguous case entirely. Any expression of +the form `$a -$b` or `$a +$b` will produce an error that will suggest the user +disambiguate by either writing `$a - $b` or `$a (-$b)`, which clearly represent +the intention to use a binary or unary operator, respectively. Other constructs +such as `($a)-$b` will still be allowed. + +As with any breaking change, we will begin by deprecating the old behavior. +Since this isn't a CSS compatibility issue, the breaking change won't land until +the next major revision of each implementation. + +## Alternatives Considered + +> This section is non-normative. + +### Spaces on Both Sides + +We considered the possibility of requiring spaces on *both* sides of binary +operators, so that `($a)-$b` would also be forbidden. However, this form is much +more likely to be interpreted as a binary operator by users, and we want to +limit how much behavior we deprecate as much as possible. + +## Syntax + +This proposal modifies the existing `SumExpression` production to forbid +this particular case: + +
+**SumExpression** ::= (SumExpression ('+' | '-')¹)? ProductExpression
+
+ +1: If there's whitespace before but not after the operator, emit a syntax error. + +## Deprecation Process + +Before an implementation releases its next major version, it should emit a +deprecation warning instead of a syntax error. From 747d8d976b8d08b7b76721208a55d661920643ab Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Jun 2022 15:21:15 -0700 Subject: [PATCH 15/41] Explicitly define `FunctionExpression` (#3336) This sets the stage for modifying it to fix #3245 --- spec/functions.md | 22 ++++++++++++++++++++++ spec/types/calculation.md | 18 +++++------------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/spec/functions.md b/spec/functions.md index a70d6ed21b..b29fa78439 100644 --- a/spec/functions.md +++ b/spec/functions.md @@ -44,6 +44,28 @@ matching is case-insensitive. ## Syntax +
+**FunctionExpression**¹ ::= [CssMinMax]
+                      | [SpecialFunctionExpression]
+                      | [CalculationExpression]
+                      | FunctionCall
+**FunctionCall**²       ::= [NamespacedIdentifier] ArgumentInvocation
+
+ +[CssMinMax]: types/calculation.md#cssminmax +[SpecialFunctionExpression]: syntax.md#specialfunctionexpression +[CalculationExpression]: types/calculation.md#calculationexpression +[NamespacedIdentifier]: modules.md#syntax + +1: If both `CssMinMax` and `FunctionCall` could be consumed, `CssMinMax` + takes precedence. + +2: `FunctionCall` may not have any whitespace between the `NamespacedIdentifier` + and the `ArgumentInvocation`. It may not start with [`SpecialFunctionName`], + `'calc('`, or `'clamp('` (case-insensitively). + +[`SpecialFunctionName`]: #specialfunctionexpression +
 **FunctionCall** ::= [NamespacedIdentifier][] ArgumentInvocation
 
diff --git a/spec/types/calculation.md b/spec/types/calculation.md index ef95681b4b..d76e92cc7e 100644 --- a/spec/types/calculation.md +++ b/spec/types/calculation.md @@ -4,7 +4,7 @@ * [Syntax](#syntax) * [`CalculationExpression`](#calculationexpression) - * [`MinMaxExpression`](#minmaxexpression) + * [`CssMinMax`](#cssminmax) * [Types](#types) * [Operations](#operations) * [Equality](#equality) @@ -18,7 +18,7 @@ * [Semantics](#semantics) * [`CalcExpression`](#calcexpression) * [`ClampExpression`](#clampexpression) - * [`CssMinMax`](#cssminmax) + * [`CssMinMax`](#cssminmax-1) * [`CalcArgument`](#calcargument) * [`CalcSum`](#calcsum) * [`CalcProduct`](#calcproduct) @@ -73,21 +73,13 @@ case-insensitively. > expression regardless of syntax, for full compatibility it's necessary to > parse it very expansively. -### `MinMaxExpression` - -This production is parsed in a SassScript context when an expression is expected -and the input stream starts with an identifier with value `min` or `max` -(ignoring case) followed immediately by `(`. +### `CssMinMax`
-**MinMaxExpression**¹ ::= CssMinMax | FunctionExpression
-**CssMinMax**         ::= ('min(' | 'max(')² CalcArgument (',' CalcArgument)* ')'
+**CssMinMax**         ::= ('min(' | 'max(')¹ CalcArgument (',' CalcArgument)* ')'
 
-1: If both `CssMinMax` and `FunctionExpression` could be consumed, `CssMinMax` - takes precedence. - -2: The strings `min(` and `max(` are matched case-insensitively. +1: The strings `min(` and `max(` are matched case-insensitively. ## Types From 8d99b48fa5e93e441095ae6ec087dd7ee9ad4862 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 21 Jun 2022 15:05:56 -0700 Subject: [PATCH 16/41] [Strict Unary] Fix a typo (#3341) --- proposal/strict-unary.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposal/strict-unary.md b/proposal/strict-unary.md index de5c7e2995..595210ab7c 100644 --- a/proposal/strict-unary.md +++ b/proposal/strict-unary.md @@ -42,11 +42,11 @@ users. > This section is non-normative. -We will address the confusion by the ambiguous case entirely. Any expression of -the form `$a -$b` or `$a +$b` will produce an error that will suggest the user -disambiguate by either writing `$a - $b` or `$a (-$b)`, which clearly represent -the intention to use a binary or unary operator, respectively. Other constructs -such as `($a)-$b` will still be allowed. +We will address the confusion by forbidding the ambiguous case entirely. Any +expression of the form `$a -$b` or `$a +$b` will produce an error that will +suggest the user disambiguate by either writing `$a - $b` or `$a (-$b)`, which +clearly represent the intention to use a binary or unary operator, respectively. +Other constructs such as `($a)-$b` will still be allowed. As with any breaking change, we will begin by deprecating the old behavior. Since this isn't a CSS compatibility issue, the breaking change won't land until From 3673b2af51cd823c0975b0377746e627097f2777 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 21 Jun 2022 15:07:43 -0700 Subject: [PATCH 17/41] Support `var()` with an empty fallback (#3338) See #3245 --- spec/functions.md | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/spec/functions.md b/spec/functions.md index b29fa78439..e695ead64a 100644 --- a/spec/functions.md +++ b/spec/functions.md @@ -7,6 +7,8 @@ * [Special Variable String](#special-variable-string) * [Syntax](#syntax) * [Semantics](#semantics) + * [`EmptyFallbackVar`:](#emptyfallbackvar) + * [`FunctionCall`](#functioncall) * [Global Functions](#global-functions) * [`adjust-hue()`](#adjust-hue) * [`alpha()`](#alpha) @@ -48,8 +50,10 @@ matching is case-insensitive. **FunctionExpression**¹ ::= [CssMinMax] | [SpecialFunctionExpression] | [CalculationExpression] + | EmptyFallbackVar | FunctionCall -**FunctionCall**² ::= [NamespacedIdentifier] ArgumentInvocation +**EmptyFallbackVar**² ::= 'var(' Expression ',' ')' +**FunctionCall**⁴ ::= [NamespacedIdentifier] ArgumentInvocation
[CssMinMax]: types/calculation.md#cssminmax @@ -57,10 +61,12 @@ matching is case-insensitive. [CalculationExpression]: types/calculation.md#calculationexpression [NamespacedIdentifier]: modules.md#syntax -1: If both `CssMinMax` and `FunctionCall` could be consumed, `CssMinMax` - takes precedence. +1: Both `CssMinMax` and `EmptyFallbackVar` take precedence over `FunctionCall` + if either could be consumed. -2: `FunctionCall` may not have any whitespace between the `NamespacedIdentifier` +2: `'var('` is matched case-insensitively. + +4: `FunctionCall` may not have any whitespace between the `NamespacedIdentifier` and the `ArgumentInvocation`. It may not start with [`SpecialFunctionName`], `'calc('`, or `'clamp('` (case-insensitively). @@ -77,14 +83,30 @@ No whitespace is allowed between the `NamespacedIdentifier` and the ## Semantics +### `EmptyFallbackVar`: + +To evaluate an `EmptyFallbackVar` `call`: + +* Let `argument` be the result of evaluating `call`'s `Expression`. + +* Let `function` be the result of [resolving a function] named `'var'`. + + [resolving a function]: modules.md#resolving-a-member + +* If `function` is null, return an unquoted string consisting of `'var('` + followed by `argument`'s CSS representation followed by `',)'`. + +* Return the result of calling `function` with `argument` as its first argument + and an empty unquoted string as its second argument. + +### `FunctionCall` + To evaluate a `FunctionCall` `call`: * Let `name` be `call`'s `NamespacedIdentifier`. * Let `function` be the result of [resolving a function][] named `name`. - [resolving a function]: modules.md#resolving-a-member - * If `function` is null and `name` is not a plain `Identifier`, throw an error. * If `function` is null, set it to the [global function](#global-functions) From 164483a56dd2d9c3c4a9280bd0663f4b2e958a14 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 21 Jun 2022 17:44:31 -0700 Subject: [PATCH 18/41] [Bogus Combinators] Create a proposal (#3342) See #3340 --- proposal/bogus-combinators.md | 223 ++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 proposal/bogus-combinators.md diff --git a/proposal/bogus-combinators.md b/proposal/bogus-combinators.md new file mode 100644 index 0000000000..05a3bf1cb5 --- /dev/null +++ b/proposal/bogus-combinators.md @@ -0,0 +1,223 @@ +# Bogus Combinators: Draft 1 + +*([Issue](https://github.com/sass/sass/issues/3340))* + +This proposal increases the strictness with which Sass parses and resolves +non-standard ("bogus") uses of selector combinators. In particular, it forbids +the use of multiple combinators in a row (such as `div + ~ a`) and limits the +use of leading combinators (such as `> a`) and trailing combinators (such as `a +>`) to selector nesting. + +## Table of Contents + +* [Background](#background) +* [Summary](#summary) + * [Phase 1: Deprecation](#phase-1-deprecation) + * [Phase 2: Removal](#phase-2-removal) +* [Definitions](#definitions) + * [Visible Combinator](#visible-combinator) + * [Complex Selector](#complex-selector) + * [Complex Selector Component](#complex-selector-component) + * [Trailing Combinator](#trailing-combinator) + * [Bogus Selector](#bogus-selector) +* [Syntax](#syntax) +* [Semantics](#semantics) + * [Evaluating a Style Rule](#evaluating-a-style-rule) +* [Functions](#functions) +* [Deprecation Process](#deprecation-process) + * [Phase 1](#phase-1) + * [Phase 2](#phase-2) + +## Background + +> This section is non-normative. + +Currently, Sass is very liberal when it comes to where explicit selector +combinators can be written—much more so than CSS itself. It allows: + +* Multiple combinators between compound selectors (`a > + b`). +* Combinators at the beginning of selectors (`> a`). +* Combinators at the end of selectors (`a >`). + +The latter two are useful when nesting style rules, but the former has no known +use at all. Historically, we've said that we support these because they enable +some browser hacks, but this seems to be purely hypothetical: [browserhacks.com] +is the most comprehensive source of hacks I've found, and it doesn't list any +hacks that are enabled by this support, even among its "legacy hacks". + +[browserhacks.com]: http://browserhacks.com + +In addition, supporting these selectors imposes substantial complexity on Sass +implementations. They [cause bugs] and make the data model more complex than it +needs to be. This in turn makes bugs like [sass/sass#1807] that don't involve +non-standard combinators more difficult to resolve. + +[cause bugs]: https://github.com/sass/dart-sass/issues/1053 +[sass/sass#1807]: https://github.com/sass/sass/issues/1807 + +## Summary + +> This section is non-normative. + +We'll move towards forbidding these combinators in two phases. + +### Phase 1: Deprecation + +In the first phase, we'll issue deprecation messages for all forbidden cases, +but only change behavior in cases where Sass was already producing invalid CSS +anyway. In particular: + +* Once nesting and extensions have been resolved, if any of a style rule's + selectors contains a leading, trailing, or multiple combinator after nesting + and extensions are resolved, omit that style rule from the generated CSS and + emit a deprecation warning. + +* If a selector with a leading or trailing combinator is used with any + extend-related infrastructure, emit a deprecation warning but *don't* change + the behavior unless the resolved selector still has a bogus combinator, as + above. + +* If a selector with a doubled combinator is used with any extend-related + infrastructure, emit a deprecation warning and treat that selector as though + it matches no elements. + +This will be sufficient to substantially simplify the implementation without +affecting the in-browser behavior of any stylesheets. + +### Phase 2: Removal + +In the second phase, which for existing implementations will accompany a major +version release, we will emit errors everywhere Phase 1 produced deprecation +warnings. In particular: + +* If a style rule's selectors contain leading, trailing, or multiple combinators + after nesting is resolved, emit an error. + +* If a selector with a leading, trailing, or multiple combinator is used with + `@extend` (which, given the previous restriction, will only be possible using + extension functions from `sass:selector`), emit an error. + +## Definitions + +> Most existing definitions being modified here haven't been defined explicitly +> before this document. The old definitions are listed in strikethrough mode to +> clarify the change. + +### Visible Combinator + +A *visible combinator* is any selector [combinator] other than the [descendant +combinator]. + +[selector combinator]: https://drafts.csswg.org/selectors-4/#combinators +[descendant combinator]: https://drafts.csswg.org/selectors-4/#descendant-combinators + +### Complex Selector + +~~A *complex selector* is a sequence of [visible combinators] (its *leading +combinators*) as well as a sequence of one or more [complex selector +components].~~ + +[visible combinators]: #visible-combinator +[complex selector components]: #complex-selector-components + +A *complex selector* is an optional [visible combinator] (its *leading +combinator*) as well as a sequence of one or more [complex selector components]. + +[visible combinator]: #visible-combinator + +### Complex Selector Component + +~~A *complex selector component* is a compound selector as well as a sequence of +zero or more [visible combinators].~~ + +A *complex selector component* is a compound selector as well as a single +[combinator]. + +### Trailing Combinator + +A [complex selector]'s *trailing combinator* is its final [complex selector +component]'s combinator if it's not a [descendant combinator]. If it *is* a +descendant combinator, the complex selector doesn't have a trailing combinator. + +[complex selector]: #complex-selector +[complex selector component]: #complex-selector-component + +### Bogus Selector + +A [complex selector] is *bogus* if it has a leading or [trailing combinator], or +if any of the simple selectors it transitively contains is a selector pseudo +with a bogus selector. + +A selector list is *bogus* if any of its complex selectors are bogus. + +[trailing combinator]: #trailing-combinator + +## Syntax + +> Note that the existing productions being modified have not been defined +> explicitly before this document. The old productions are listed in +> strikethrough mode to clarify the change. + +This proposal modifies the existing `ComplexSelector` and +`ComplexSelectorComponent` productions to drop support for multiple combinators: + +
+~~**ComplexSelector**          ::= Combinator* ComplexSelectorComponent+~~
+~~**ComplexSelectorComponent** ::= CompoundSelector Combinator*~~
+**ComplexSelector**          ::= Combinator? ComplexSelectorComponent+
+**ComplexSelectorComponent** ::= CompoundSelector Combinator?
+**Combinator**               ::= '+' | '>' | '~'
+
+ +## Semantics + +### Evaluating a Style Rule + +This proposal adds the following to [Evaluating a Style Rule], before +creating a CSS style rule: + +[Evaluating a Style Rule]: ../spec/style-rules.md#semantics + +* If `selector` is [bogus], throw an error. + +[bogus]: #bogus-selector + +## Functions + +For the `selector.append()`, `selector.extend()`, `selector.is-superselector()`, +`selector.replace()`, and `selector.unify()` functions, after parsing their +selector arguments, throw an error if any of the parsed selectors are [bogus]. + +> `selector.nest()` is still allowed to take bogus selectors, because they're +> explicitly useful for nesting. + +## Deprecation Process + +The deprecation will be divided into two phases: + +### Phase 1 + +This phase will only change behavior that doesn't affect in-browser rendering. +In particular: + +* The parsing of `ComplexSelector` and `ComplexSelectorComponent` is unchanged. + +* A complex selector is instead considered [bogus] if it contains any leading + combinators, if its final component contains any combinators, or if any of its + components contains multiple combinators, or if any of the simple selectors it + transitively contains is a selector pseudo with a bogus selector. + +* The newly-added errors produce deprecation warnings instead. + +* In [Evalutating a Style Rule], only append `css` to the current module's CSS + if its selector is not [bogus]. + +* In [Extending a Selector], if a complex selector has multiple combinators, or + if any of its components has multiple combinators, treat it as a selector that + can match no elements. + + [Extending a Selector]: ../spec/at-rules/extend.md#extending-a-selector + +### Phase 2 + +This phase will emit errors as described in the body of the proposal. From 0a5bbfe341f2ac290a1ccafb3855550c33207b7b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 23 Jun 2022 16:23:32 -0700 Subject: [PATCH 19/41] [Media Logic] Add a proposal (#3344) --- proposal/media-logic.md | 129 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 proposal/media-logic.md diff --git a/proposal/media-logic.md b/proposal/media-logic.md new file mode 100644 index 0000000000..59d4d4c321 --- /dev/null +++ b/proposal/media-logic.md @@ -0,0 +1,129 @@ +# Media Logic: Draft 1 + +*([Issue](https://github.com/sass/sass/issues/2538))* + +This proposal adds support for the full [Media Queries Level 4] syntax for media +conditions, including arbitrary boolean logic using `and`, `or`, and `not`. + +[Media Queries Level 4]: https://www.w3.org/TR/mediaqueries-4/#media-conditions + +## Table of Contents + +* [Background](#background) +* [Summary](#summary) +* [Syntax](#syntax) + * [`MediaQuery`](#mediaquery) + * [`CssMediaQuery`](#cssmediaquery) +* [Deprecation Process](#deprecation-process) + +## Background + +> This section is non-normative. + +For historical reasons, Sass fully parses media queries and allows SassScript to +be embedded directly in them, as in `@media ($query: $value)`, in contrast to +most other at-rules in which SassScript can only be injected using +interpolation. This means that as CSS adds new media query syntax, Sass is +obligated to update its specification to accommodate it. + +[Media Queries Level 4] adds support for arbitrary boolean logic in media +queries, such as `@media ((width >= 100px) and (width <= 800px)) or (grid)`. +Sass must therefore update its syntax accordingly. + +## Summary + +> This section is non-normative. + +The proposal is relatively straightforward: it adds the new syntax to Sass's +grammar. It is worth noting, though, that this will require a few breaking +changes. These are unlikely to affect many real-world stylesheets, but they're +worth highlighting nevertheless. + +The new syntax allows any [``] to appear inside a +[``]. This means that queries beginning with `(not ` or `((` +must be parsed as nested media queries, rather than SassScript expressions as +they have historically been parsed. We'll issue a short deprecation period for +the SassScript expressions in question, recommending users migrate them to +interpolation instead, then drop support and begin parsing them as media queries +for CSS compatibility. + +[``]: https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition +[``]: https://drafts.csswg.org/mediaqueries-4/#typedef-media-in-parens + +## Syntax + +### `MediaQuery` + +Replace the definition of the [`MediaQuery`] production with the following (with +all identifiers matched case-insensitively): + +[`MediaQuery`]: ../spec/at-rules/media.md#sass + +
+**MediaQuery**     ::= MediaNot
+                 | MediaOrInterp (MediaAnd* | MediaOr*)
+                 | MediaType ('and' MediaNot | MediaAnd*)
+**MediaType**      ::= [InterpolatedIdentifier] [InterpolatedIdentifier]¹?
+**MediaNot**       ::= 'not' MediaOrInterp
+**MediaAnd**       ::= 'and' MediaOrInterp
+**MediaOr**        ::= 'or' MediaOrInterp
+**MediaOrInterp**  ::= Interpolation | MediaInParens
+**MediaInParens**  ::= '(' Expression² ')'
+                 | '(' Expression² [\] Expression² ')'
+                 | '(' Expression² [\] Expression² [\] Expression² ')'
+                 | '(' Expression² [\] Expression² [\] Expression² ')'
+                 | '(' MediaNot ')'
+                 | '(' MediaInParens (MediaAnd* | MediaOr*) ')'
+
+ +[InterpolatedIdentifier]: ../syntax.md#interpolatedidentifier +[\]: https://drafts.csswg.org/mediaqueries-4/#typedef-mf-comparison +[\]: https://drafts.csswg.org/mediaqueries-4/#typedef-mf-lt +[\]: https://drafts.csswg.org/mediaqueries-4/#typedef-mf-gt + +1. This `InterpolatedIdentifier` may not be the identifier `"and"`. + +2. These `Expression`s may not: + + * Contain binary operator expressions with the operators `=`, `>`, `>=`, `<`, + or `<=`, except within parentheses (including function calls and map + literals) and square brackets. + + * Begin with the case-insensitive identifier `"not"`. + + * Begin with the character `"("`. + +### `CssMediaQuery` + +Replace the definition of the [`CssMediaQuery`] production with the following (with +all identifiers matched case-insensitively): + +[`MediaQuery`]: ../spec/at-rules/media.md#css + +
+**CssMediaQuery**     ::= CssMediaCondition
+                    | CssMediaType ('and' CssMediaNot | CssMediaAnd*)
+**CssMediaType**      ::= [\] [\]¹?
+**CssMediaCondition** ::= CssMediaNot | CssMediaInParens (CssMediaAnd* | CssMediaOr*)
+**CssMediaNot**       ::= 'not' CssMediaInParens
+**CssMediaAnd**       ::= 'and' CssMediaInParens
+**CssMediaOr**        ::= 'or' CssMediaInParens
+**CssMediaInParens**  ::= '(' [\] ')'
+
+ +[\]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram +[\]: https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value + +1. This `` may not be the identifier `"and"`. + +## Deprecation Process + +Before this specification is applied in full force, it will be applied with the +following modifications: + +* [`MediaInParens`](#mediaquery) will not allow the productions `'(' MediaNot + ')'` or `'(' MediaInParens (MediaAnd* | MediaOr*) ')'`. + +* If the first `Expression` in a `MediaInParens` production begins with the + case-insensitive identifier `"not"` or the character `"("`, emit a deprecation + warning. From 8a2bbf482316a5e62f54b04a6582b5d9e9bf4acd Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 24 Jun 2022 14:04:23 -0700 Subject: [PATCH 20/41] [Bogus Combinators] Support leading combinators in `:has()` (#3345) --- proposal/bogus-combinators.changes.md | 9 +++++++++ proposal/bogus-combinators.md | 27 ++++++++++++++++----------- 2 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 proposal/bogus-combinators.changes.md diff --git a/proposal/bogus-combinators.changes.md b/proposal/bogus-combinators.changes.md new file mode 100644 index 0000000000..d55f94ad20 --- /dev/null +++ b/proposal/bogus-combinators.changes.md @@ -0,0 +1,9 @@ +## Draft 2 + +* Allow leading combinators in `:has()`. + +* Replace our custom `Combinator` production with the CSS spec's ``. + +## Draft 1 + +* Initial draft. diff --git a/proposal/bogus-combinators.md b/proposal/bogus-combinators.md index 05a3bf1cb5..90105b582c 100644 --- a/proposal/bogus-combinators.md +++ b/proposal/bogus-combinators.md @@ -1,6 +1,6 @@ -# Bogus Combinators: Draft 1 +# Bogus Combinators: Draft 2 -*([Issue](https://github.com/sass/sass/issues/3340))* +*([Issue](https://github.com/sass/sass/issues/3340), [Changelog](bogus-combinators.changes.md))* This proposal increases the strictness with which Sass parses and resolves non-standard ("bogus") uses of selector combinators. In particular, it forbids @@ -21,6 +21,7 @@ use of leading combinators (such as `> a`) and trailing combinators (such as `a * [Trailing Combinator](#trailing-combinator) * [Bogus Selector](#bogus-selector) * [Syntax](#syntax) + * [`ComplexSelector`](#complexselector) * [Semantics](#semantics) * [Evaluating a Style Rule](#evaluating-a-style-rule) * [Functions](#functions) @@ -108,7 +109,7 @@ warnings. In particular: A *visible combinator* is any selector [combinator] other than the [descendant combinator]. -[selector combinator]: https://drafts.csswg.org/selectors-4/#combinators +[combinator]: https://drafts.csswg.org/selectors-4/#combinators [descendant combinator]: https://drafts.csswg.org/selectors-4/#descendant-combinators ### Complex Selector @@ -146,7 +147,8 @@ descendant combinator, the complex selector doesn't have a trailing combinator. A [complex selector] is *bogus* if it has a leading or [trailing combinator], or if any of the simple selectors it transitively contains is a selector pseudo -with a bogus selector. +with a bogus selector, except that `:has()` may contain complex selectors with +leading combinators. A selector list is *bogus* if any of its complex selectors are bogus. @@ -154,6 +156,8 @@ A selector list is *bogus* if any of its complex selectors are bogus. ## Syntax +### `ComplexSelector` + > Note that the existing productions being modified have not been defined > explicitly before this document. The old productions are listed in > strikethrough mode to clarify the change. @@ -162,13 +166,14 @@ This proposal modifies the existing `ComplexSelector` and `ComplexSelectorComponent` productions to drop support for multiple combinators:
-~~**ComplexSelector**          ::= Combinator* ComplexSelectorComponent+~~
-~~**ComplexSelectorComponent** ::= CompoundSelector Combinator*~~
-**ComplexSelector**          ::= Combinator? ComplexSelectorComponent+
-**ComplexSelectorComponent** ::= CompoundSelector Combinator?
-**Combinator**               ::= '+' | '>' | '~'
+~~**ComplexSelector**          ::= [\]* ComplexSelectorComponent+~~
+~~**ComplexSelectorComponent** ::= CompoundSelector [\]*~~
+**ComplexSelector**          ::= [\]? ComplexSelectorComponent+
+**ComplexSelectorComponent** ::= CompoundSelector [\]?
 
+[\]: https://drafts.csswg.org/selectors-4/#typedef-combinator + ## Semantics ### Evaluating a Style Rule @@ -209,8 +214,8 @@ In particular: * The newly-added errors produce deprecation warnings instead. -* In [Evalutating a Style Rule], only append `css` to the current module's CSS - if its selector is not [bogus]. +* In [Evaluating a Style Rule], only append `css` to the current module's CSS if + its selector is not [bogus]. * In [Extending a Selector], if a complex selector has multiple combinators, or if any of its components has multiple combinators, treat it as a selector that From c1850ac85dec7cfcf0bd35c44473bfa9f15a01bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Wed, 29 Jun 2022 16:25:21 -0700 Subject: [PATCH 21/41] Add charset option to js-api (#3346) --- js-api-doc/options.d.ts | 12 ++++++++++++ spec/js-api/options.d.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts index 6d09256a42..1a12b3bcef 100644 --- a/js-api-doc/options.d.ts +++ b/js-api-doc/options.d.ts @@ -108,6 +108,18 @@ export interface Options { */ alertColor?: boolean; + /** + * If `true`, the compiler may prepend `@charset "UTF-8";` or U+FEFF + * (byte-order marker) if it outputs non-ASCII CSS. + * + * If `false`, the compiler never emits these byte sequences. This is ideal + * when concatenating or embedding in HTML `