From 8c2dd4e6f4138dede6c48cd0b1582a2f84dc850e Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 12 Sep 2025 17:31:41 -0700 Subject: [PATCH 1/2] Add specs for modern `@extend` in `:is()`/`:has()`/`:where()` --- .../selector/extend/in_is/has.hrx | 173 +++++++ .../selector/extend/in_is/is.hrx | 173 +++++++ .../selector/extend/in_is/where.hrx | 173 +++++++ spec/core_functions/selector/extend/no_op.hrx | 2 +- .../simple/pseudo/selector/idempotent/is.hrx | 11 + .../pseudo/selector/idempotent/where.hrx | 13 +- spec/core_functions/selector/replace.hrx | 436 ------------------ .../core_functions/selector/replace/README.md | 4 + .../selector/replace/complex.hrx | 8 + .../selector/replace/compound.hrx | 8 + .../core_functions/selector/replace/error.hrx | 228 +++++++++ .../selector/replace/format.hrx | 87 ++++ .../core_functions/selector/replace/in_is.hrx | 173 +++++++ .../core_functions/selector/replace/named.hrx | 8 + .../core_functions/selector/replace/no_op.hrx | 8 + .../selector/replace/partial_no_op.hrx | 8 + .../selector/replace/selector_pseudo.hrx | 41 ++ .../selector/replace/simple.hrx | 8 + spec/directives/extend/in_is/after_target.hrx | 20 + spec/directives/extend/{ => in_is}/pseudo.hrx | 0 spec/directives/extend/in_is/trim.hrx | 192 ++++++++ .../extend/{ => top_level}/after_target.hrx | 0 ...rims_super_selector_without_combinator.hrx | 0 spec/libsass-closed-issues/issue_2055.hrx | 6 +- 24 files changed, 1339 insertions(+), 441 deletions(-) create mode 100644 spec/core_functions/selector/extend/in_is/has.hrx create mode 100644 spec/core_functions/selector/extend/in_is/is.hrx create mode 100644 spec/core_functions/selector/extend/in_is/where.hrx delete mode 100644 spec/core_functions/selector/replace.hrx create mode 100644 spec/core_functions/selector/replace/README.md create mode 100644 spec/core_functions/selector/replace/complex.hrx create mode 100644 spec/core_functions/selector/replace/compound.hrx create mode 100644 spec/core_functions/selector/replace/error.hrx create mode 100644 spec/core_functions/selector/replace/format.hrx create mode 100644 spec/core_functions/selector/replace/in_is.hrx create mode 100644 spec/core_functions/selector/replace/named.hrx create mode 100644 spec/core_functions/selector/replace/no_op.hrx create mode 100644 spec/core_functions/selector/replace/partial_no_op.hrx create mode 100644 spec/core_functions/selector/replace/selector_pseudo.hrx create mode 100644 spec/core_functions/selector/replace/simple.hrx create mode 100644 spec/directives/extend/in_is/after_target.hrx rename spec/directives/extend/{ => in_is}/pseudo.hrx (100%) create mode 100644 spec/directives/extend/in_is/trim.hrx rename spec/directives/extend/{ => top_level}/after_target.hrx (100%) rename spec/directives/extend/{ => top_level}/trims_super_selector_without_combinator.hrx (100%) diff --git a/spec/core_functions/selector/extend/in_is/has.hrx b/spec/core_functions/selector/extend/in_is/has.hrx new file mode 100644 index 0000000000..d9128bbf85 --- /dev/null +++ b/spec/core_functions/selector/extend/in_is/has.hrx @@ -0,0 +1,173 @@ +<===> simple_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c)", ".c", ".d")} + +<===> simple_target/single_extender/output.css +a { + b: :has(.c, .d); +} + +<===> +================================================================================ +<===> simple_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c)", ".c", ".d.e")} + +<===> simple_target/compound_extender/output.css +a { + b: :has(.c, .d.e); +} + +<===> +================================================================================ +<===> simple_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c)", ".c", ".d .e")} + +<===> simple_target/complex_extender/output.css +a { + b: :has(.c, .d .e); +} + +<===> +================================================================================ +<===> simple_target/lhast_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c)", ".c", ".d, .e")} + +<===> simple_target/lhast_extender/output.css +a { + b: :has(.c, .d, .e); +} + +<===> +================================================================================ +<===> compound_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c.d)", ".c", ".e")} + +<===> compound_target/single_extender/output.css +a { + b: :has(.d:is(.c, .e)); +} + +<===> +================================================================================ +<===> compound_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c.d)", ".c", ".e.f")} + +<===> compound_target/compound_extender/output.css +a { + b: :has(.d:is(.c, .e.f)); +} + +<===> +================================================================================ +<===> compound_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c.d)", ".c", ".e .f")} + +<===> compound_target/complex_extender/output.css +a { + b: :has(.d:is(.c, .e .f)); +} + +<===> +================================================================================ +<===> compound_target/lhast_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c.d)", ".c", ".e, .f")} + +<===> compound_target/lhast_extender/output.css +a { + b: :has(.d:is(.c, .e, .f)); +} + +<===> +================================================================================ +<===> complex_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c .d)", ".d", ".e")} + +<===> complex_target/single_extender/output.css +a { + b: :has(.c :is(.d, .e)); +} + +<===> +================================================================================ +<===> complex_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c .d)", ".d", ".e.f")} + +<===> complex_target/compound_extender/output.css +a { + b: :has(.c :is(.d, .e.f)); +} + +<===> +================================================================================ +<===> complex_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c .d)", ".d", ".e .f")} + +<===> complex_target/complex_extender/output.css +a { + b: :has(.c :is(.d, .e .f)); +} + +<===> +================================================================================ +<===> complex_target/lhast_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c .d)", ".d", ".e, .f")} + +<===> complex_target/lhast_extender/output.css +a { + b: :has(.c :is(.d, .e, .f)); +} + +<===> +================================================================================ +<===> lhast_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c, .d)", ".d", ".e")} + +<===> lhast_target/single_extender/output.css +a { + b: :has(.c, .d, .e); +} + +<===> +================================================================================ +<===> lhast_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c, .d)", ".d", ".e.f")} + +<===> lhast_target/compound_extender/output.css +a { + b: :has(.c, .d, .e.f); +} + +<===> +================================================================================ +<===> lhast_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c, .d)", ".d", ".e .f")} + +<===> lhast_target/complex_extender/output.css +a { + b: :has(.c, .d, .e .f); +} + +<===> +================================================================================ +<===> lhast_target/lhast_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":has(.c, .d)", ".d", ".e, .f")} + +<===> lhast_target/lhast_extender/output.css +a { + b: :has(.c, .d, .e, .f); +} diff --git a/spec/core_functions/selector/extend/in_is/is.hrx b/spec/core_functions/selector/extend/in_is/is.hrx new file mode 100644 index 0000000000..af31434cf8 --- /dev/null +++ b/spec/core_functions/selector/extend/in_is/is.hrx @@ -0,0 +1,173 @@ +<===> simple_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c)", ".c", ".d")} + +<===> simple_target/single_extender/output.css +a { + b: :is(.c, .d); +} + +<===> +================================================================================ +<===> simple_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c)", ".c", ".d.e")} + +<===> simple_target/compound_extender/output.css +a { + b: :is(.c, .d.e); +} + +<===> +================================================================================ +<===> simple_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c)", ".c", ".d .e")} + +<===> simple_target/complex_extender/output.css +a { + b: :is(.c, .d .e); +} + +<===> +================================================================================ +<===> simple_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c)", ".c", ".d, .e")} + +<===> simple_target/list_extender/output.css +a { + b: :is(.c, .d, .e); +} + +<===> +================================================================================ +<===> compound_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c.d)", ".c", ".e")} + +<===> compound_target/single_extender/output.css +a { + b: .d:is(.c, .e); +} + +<===> +================================================================================ +<===> compound_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c.d)", ".c", ".e.f")} + +<===> compound_target/compound_extender/output.css +a { + b: .d:is(.c, .e.f); +} + +<===> +================================================================================ +<===> compound_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c.d)", ".c", ".e .f")} + +<===> compound_target/complex_extender/output.css +a { + b: .d:is(.c, .e .f); +} + +<===> +================================================================================ +<===> compound_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c.d)", ".c", ".e, .f")} + +<===> compound_target/list_extender/output.css +a { + b: .d:is(.c, .e, .f); +} + +<===> +================================================================================ +<===> complex_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c .d)", ".d", ".e")} + +<===> complex_target/single_extender/output.css +a { + b: :is(.c :is(.d, .e)); +} + +<===> +================================================================================ +<===> complex_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c .d)", ".d", ".e.f")} + +<===> complex_target/compound_extender/output.css +a { + b: :is(.c :is(.d, .e.f)); +} + +<===> +================================================================================ +<===> complex_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c .d)", ".d", ".e .f")} + +<===> complex_target/complex_extender/output.css +a { + b: :is(.c :is(.d, .e .f)); +} + +<===> +================================================================================ +<===> complex_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c .d)", ".d", ".e, .f")} + +<===> complex_target/list_extender/output.css +a { + b: :is(.c :is(.d, .e, .f)); +} + +<===> +================================================================================ +<===> list_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c, .d)", ".d", ".e")} + +<===> list_target/single_extender/output.css +a { + b: :is(.c, .d, .e); +} + +<===> +================================================================================ +<===> list_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c, .d)", ".d", ".e.f")} + +<===> list_target/compound_extender/output.css +a { + b: :is(.c, .d, .e.f); +} + +<===> +================================================================================ +<===> list_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c, .d)", ".d", ".e .f")} + +<===> list_target/complex_extender/output.css +a { + b: :is(.c, .d, .e .f); +} + +<===> +================================================================================ +<===> list_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c, .d)", ".d", ".e, .f")} + +<===> list_target/list_extender/output.css +a { + b: :is(.c, .d, .e, .f); +} diff --git a/spec/core_functions/selector/extend/in_is/where.hrx b/spec/core_functions/selector/extend/in_is/where.hrx new file mode 100644 index 0000000000..d80790b41f --- /dev/null +++ b/spec/core_functions/selector/extend/in_is/where.hrx @@ -0,0 +1,173 @@ +<===> simple_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c)", ".c", ".d")} + +<===> simple_target/single_extender/output.css +a { + b: :where(.c, .d); +} + +<===> +================================================================================ +<===> simple_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c)", ".c", ".d.e")} + +<===> simple_target/compound_extender/output.css +a { + b: :where(.c, .d.e); +} + +<===> +================================================================================ +<===> simple_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c)", ".c", ".d .e")} + +<===> simple_target/complex_extender/output.css +a { + b: :where(.c, .d .e); +} + +<===> +================================================================================ +<===> simple_target/lwheret_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c)", ".c", ".d, .e")} + +<===> simple_target/lwheret_extender/output.css +a { + b: :where(.c, .d, .e); +} + +<===> +================================================================================ +<===> compound_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c.d)", ".c", ".e")} + +<===> compound_target/single_extender/output.css +a { + b: :where(.d:is(.c, .e)); +} + +<===> +================================================================================ +<===> compound_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c.d)", ".c", ".e.f")} + +<===> compound_target/compound_extender/output.css +a { + b: :where(.d:is(.c, .e.f)); +} + +<===> +================================================================================ +<===> compound_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c.d)", ".c", ".e .f")} + +<===> compound_target/complex_extender/output.css +a { + b: :where(.d:is(.c, .e .f)); +} + +<===> +================================================================================ +<===> compound_target/lwheret_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c.d)", ".c", ".e, .f")} + +<===> compound_target/lwheret_extender/output.css +a { + b: :where(.d:is(.c, .e, .f)); +} + +<===> +================================================================================ +<===> complex_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c .d)", ".d", ".e")} + +<===> complex_target/single_extender/output.css +a { + b: :where(.c :is(.d, .e)); +} + +<===> +================================================================================ +<===> complex_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c .d)", ".d", ".e.f")} + +<===> complex_target/compound_extender/output.css +a { + b: :where(.c :is(.d, .e.f)); +} + +<===> +================================================================================ +<===> complex_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c .d)", ".d", ".e .f")} + +<===> complex_target/complex_extender/output.css +a { + b: :where(.c :is(.d, .e .f)); +} + +<===> +================================================================================ +<===> complex_target/lwheret_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c .d)", ".d", ".e, .f")} + +<===> complex_target/lwheret_extender/output.css +a { + b: :where(.c :is(.d, .e, .f)); +} + +<===> +================================================================================ +<===> lwheret_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c, .d)", ".d", ".e")} + +<===> lwheret_target/single_extender/output.css +a { + b: :where(.c, .d, .e); +} + +<===> +================================================================================ +<===> lwheret_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c, .d)", ".d", ".e.f")} + +<===> lwheret_target/compound_extender/output.css +a { + b: :where(.c, .d, .e.f); +} + +<===> +================================================================================ +<===> lwheret_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c, .d)", ".d", ".e .f")} + +<===> lwheret_target/complex_extender/output.css +a { + b: :where(.c, .d, .e .f); +} + +<===> +================================================================================ +<===> lwheret_target/lwheret_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c, .d)", ".d", ".e, .f")} + +<===> lwheret_target/lwheret_extender/output.css +a { + b: :where(.c, .d, .e, .f); +} diff --git a/spec/core_functions/selector/extend/no_op.hrx b/spec/core_functions/selector/extend/no_op.hrx index 18503083ad..c91da625a9 100644 --- a/spec/core_functions/selector/extend/no_op.hrx +++ b/spec/core_functions/selector/extend/no_op.hrx @@ -246,7 +246,7 @@ a {b: selector.extend(".c:is(d)", ":is(d)", "d.e")} <===> unification/subselector_of_target/is/output.css a { - b: .c:is(d); + b: d.c; } <===> diff --git a/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/is.hrx b/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/is.hrx index 1629c81e52..c5487cf2d4 100644 --- a/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/is.hrx +++ b/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/is.hrx @@ -28,3 +28,14 @@ a {b: selector.extend(":is(.c)", ".c", ":is(.d, .e)")} a { b: :is(.c, .d, .e); } + +<===> +================================================================================ +<===> where_in_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":is(.c)", ".c", ":where(.d, .e)")} + +<===> where_in_extender/output.css +a { + b: :is(.c, :where(.d, .e)); +} diff --git a/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/where.hrx b/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/where.hrx index 92e380b386..5e2e18454f 100644 --- a/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/where.hrx +++ b/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent/where.hrx @@ -22,9 +22,20 @@ a { ================================================================================ <===> is_in_extender/input.scss @use "sass:selector"; -a {b: selector.extend(":where(.c)", ".c", ":where(.d, .e)")} +a {b: selector.extend(":where(.c)", ".c", ":is(.d, .e)")} <===> is_in_extender/output.css a { b: :where(.c, .d, .e); } + +<===> +================================================================================ +<===> where_in_extender/input.scss +@use "sass:selector"; +a {b: selector.extend(":where(.c)", ".c", ":where(.d, .e)")} + +<===> where_in_extender/output.css +a { + b: :where(.c, .d, .e); +} diff --git a/spec/core_functions/selector/replace.hrx b/spec/core_functions/selector/replace.hrx deleted file mode 100644 index 203c25c631..0000000000 --- a/spec/core_functions/selector/replace.hrx +++ /dev/null @@ -1,436 +0,0 @@ -<===> README.md -`selector-replace()` is expected to have all the same complexity of behavior as -`selector-extend()`, but to avoid unnecessary duplication those specs aren't -repeated here. Instead, these specs cover the major differences between the two -functions. - -<===> -================================================================================ -<===> simple/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "c", "d")} - -<===> simple/output.css -a { - b: d; -} - -<===> -================================================================================ -<===> compound/input.scss -@use "sass:selector"; -a {b: selector.replace("c.d", "c", "e")} - -<===> compound/output.css -a { - b: e.d; -} - -<===> -================================================================================ -<===> complex/input.scss -@use "sass:selector"; -a {b: selector.replace("c d", "d", "e f")} - -<===> complex/output.css -a { - b: c e f, e c f; -} - -<===> -================================================================================ -<===> selector_pseudo/is/input.scss -@use "sass:selector"; -a {b: selector.replace(":is(c)", "c", "d")} - -<===> selector_pseudo/is/output.css -a { - b: :is(d); -} - -<===> -================================================================================ -<===> selector_pseudo/where/input.scss -@use "sass:selector"; -a {b: selector.replace(":where(c)", "c", "d")} - -<===> selector_pseudo/where/output.css -a { - b: :where(d); -} - -<===> -================================================================================ -<===> selector_pseudo/matches/input.scss -@use "sass:selector"; -a {b: selector.replace(":matches(c)", "c", "d")} - -<===> selector_pseudo/matches/output.css -a { - b: :matches(d); -} - -<===> -================================================================================ -<===> selector_pseudo/not/input.scss -@use "sass:selector"; -a {b: selector.replace(":not(c)", "c", "d")} - -<===> selector_pseudo/not/output.css -a { - b: :not(d); -} - -<===> -================================================================================ -<===> no_op/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d", "e")} - -<===> no_op/output.css -a { - b: c; -} - -<===> -================================================================================ -<===> partial_no_op/input.scss -@use "sass:selector"; -a {b: selector.replace("c, d", "d", "e")} - -<===> partial_no_op/output.css -a { - b: c, e; -} - -<===> -================================================================================ -<===> format/input/non_string/README.md -These specs verify that all the arguments to `selector-extend()` can take the -parsed selector format, and that the function returns a selector in that format. -The full set of possible input formats is tested with `selector-parse()`; this -spec just verifies one example for each parameter. - -<===> -================================================================================ -<===> format/input/non_string/selector/input.scss -@use "sass:selector"; -a {b: selector.replace((c, d c), "c", "e")} - -<===> format/input/non_string/selector/output.css -a { - b: e, d e; -} - -<===> -================================================================================ -<===> format/input/non_string/extendee/input.scss -@use "sass:selector"; -a {b: selector.replace("c.d", (c, ".d"), ".e")} - -<===> format/input/non_string/extendee/output.css -a { - b: .e; -} - -<===> -================================================================================ -<===> format/input/non_string/extender/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "c", (d, e f))} - -<===> format/input/non_string/extender/output.css -a { - b: d, e f; -} - -<===> -================================================================================ -<===> format/input/multiple_extendees/compound/input.scss -@use "sass:selector"; -a {b: selector.replace("c.d", "c.d", ".e")} - -<===> format/input/multiple_extendees/compound/output.css -a { - b: .e; -} - -<===> -================================================================================ -<===> format/input/multiple_extendees/list/input.scss -@use "sass:selector"; -a {b: selector.replace("c.d", "c, .d", ".e")} - -<===> format/input/multiple_extendees/list/output.css -a { - b: .e; -} - -<===> -================================================================================ -<===> format/input/multiple_extendees/list_of_compound/input.scss -@use "sass:selector"; -a {b: selector.replace("c.d.e.f", "c.d, .e.f", ".g")} - -<===> format/input/multiple_extendees/list_of_compound/output.css -a { - b: .g; -} - -<===> -================================================================================ -<===> format/output/input.scss -@use "sass:selector"; -$result: selector.replace("c d, e f", "g", "g"); -a { - result: $result; - structure: $result == ("c" "d", "e" "f"); -} - -<===> format/output/output.css -a { - result: c d, e f; - structure: true; -} - -<===> -================================================================================ -<===> named/input.scss -@use "sass:selector"; -a {b: selector.replace($selector: "c.d", $original: "c", $replacement: "e")} - -<===> named/output.css -a { - b: e.d; -} - -<===> -================================================================================ -<===> error/wrong_name/input.scss -@use "sass:selector"; -a {b: selector.selector-replace(c, c, d)} - -<===> error/wrong_name/error -Error: Undefined function. - , -2 | a {b: selector.selector-replace(c, c, d)} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/selector/parent/input.scss -@use "sass:selector"; -a {b: selector.replace("&", "c", "d")} - -<===> error/selector/parent/error -Error: $selector: Parent selectors aren't allowed here. - , -1 | & - | ^ - ' - ,--> input.scss -2 | a {b: selector.replace("&", "c", "d")} - | =============================== invocation - ' - - 1:1 root stylesheet - -<===> -================================================================================ -<===> error/selector/invalid/input.scss -@use "sass:selector"; -a {b: selector.replace("[c", "d", "e")} - -<===> error/selector/invalid/error -Error: $selector: expected more input. - , -1 | [c - | ^ - ' - ,--> input.scss -2 | a {b: selector.replace("[c", "d", "e")} - | ================================ invocation - ' - - 1:3 root stylesheet - -<===> -================================================================================ -<===> error/selector/type/input.scss -@use "sass:selector"; -a {b: selector.replace(1, "c", "d")} - -<===> error/selector/type/error -Error: $selector: 1 is not a valid selector: it must be a string, -a list of strings, or a list of lists of strings. - , -2 | a {b: selector.replace(1, "c", "d")} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/extendee/parent/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "&", "d")} - -<===> error/extendee/parent/error -Error: $original: Parent selectors aren't allowed here. - , -1 | & - | ^ - ' - ,--> input.scss -2 | a {b: selector.replace("c", "&", "d")} - | =============================== invocation - ' - - 1:1 root stylesheet - -<===> -================================================================================ -<===> error/extendee/complex/string/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d e", "f")} - -<===> error/extendee/complex/string/error -Error: Can't extend complex selector d e. - , -2 | a {b: selector.replace("c", "d e", "f")} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/extendee/complex/list/input.scss -@use "sass:selector"; -a {b: selector.replace("c", d e, "f")} - -<===> error/extendee/complex/list/error -Error: Can't extend complex selector d e. - , -2 | a {b: selector.replace("c", d e, "f")} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/extendee/invalid/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "[d", "e")} - -<===> error/extendee/invalid/error -Error: $original: expected more input. - , -1 | [d - | ^ - ' - ,--> input.scss -2 | a {b: selector.replace("c", "[d", "e")} - | ================================ invocation - ' - - 1:3 root stylesheet - -<===> -================================================================================ -<===> error/extendee/type/input.scss -@use "sass:selector"; -a {b: selector.replace("c", 1, "d")} - -<===> error/extendee/type/error -Error: $original: 1 is not a valid selector: it must be a string, -a list of strings, or a list of lists of strings. - , -2 | a {b: selector.replace("c", 1, "d")} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/extender/parent/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d", "&")} - -<===> error/extender/parent/error -Error: $replacement: Parent selectors aren't allowed here. - , -1 | & - | ^ - ' - ,--> input.scss -2 | a {b: selector.replace("c", "d", "&")} - | =============================== invocation - ' - - 1:1 root stylesheet - -<===> -================================================================================ -<===> error/extender/invalid/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d", "[e")} - -<===> error/extender/invalid/error -Error: $replacement: expected more input. - , -1 | [e - | ^ - ' - ,--> input.scss -2 | a {b: selector.replace("c", "d", "[e")} - | ================================ invocation - ' - - 1:3 root stylesheet - -<===> -================================================================================ -<===> error/extender/type/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d", 1)} - -<===> error/extender/type/error -Error: $replacement: 1 is not a valid selector: it must be a string, -a list of strings, or a list of lists of strings. - , -2 | a {b: selector.replace("c", "d", 1)} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/too_many_args/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d", "e", "f")} - -<===> error/too_many_args/error -Error: Only 3 arguments allowed, but 4 were passed. - ,--> input.scss -2 | a {b: selector.replace("c", "d", "e", "f")} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invocation - ' - ,--> sass:selector -1 | @function replace($selector, $original, $replacement) { - | =========================================== declaration - ' - input.scss 2:7 root stylesheet - -<===> -================================================================================ -<===> error/too_few_args/input.scss -@use "sass:selector"; -a {b: selector.replace("c", "d")} - -<===> error/too_few_args/error -Error: Missing argument $replacement. - ,--> input.scss -2 | a {b: selector.replace("c", "d")} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ invocation - ' - ,--> sass:selector -1 | @function replace($selector, $original, $replacement) { - | =========================================== declaration - ' - input.scss 2:7 root stylesheet diff --git a/spec/core_functions/selector/replace/README.md b/spec/core_functions/selector/replace/README.md new file mode 100644 index 0000000000..1314d255ee --- /dev/null +++ b/spec/core_functions/selector/replace/README.md @@ -0,0 +1,4 @@ +`selector-replace()` is expected to have all the same complexity of behavior as +`selector-extend()`, but to avoid unnecessary duplication those specs aren't +repeated here. Instead, these specs cover the major differences between the two +functions. diff --git a/spec/core_functions/selector/replace/complex.hrx b/spec/core_functions/selector/replace/complex.hrx new file mode 100644 index 0000000000..82a77f4105 --- /dev/null +++ b/spec/core_functions/selector/replace/complex.hrx @@ -0,0 +1,8 @@ +<===> input.scss +@use "sass:selector"; +a {b: selector.replace("c d", "d", "e f")} + +<===> output.css +a { + b: c e f, e c f; +} diff --git a/spec/core_functions/selector/replace/compound.hrx b/spec/core_functions/selector/replace/compound.hrx new file mode 100644 index 0000000000..322cd921b3 --- /dev/null +++ b/spec/core_functions/selector/replace/compound.hrx @@ -0,0 +1,8 @@ +<===> input.scss +@use "sass:selector"; +a {b: selector.replace("c.d", "c", "e")} + +<===> output.css +a { + b: e.d; +} diff --git a/spec/core_functions/selector/replace/error.hrx b/spec/core_functions/selector/replace/error.hrx new file mode 100644 index 0000000000..b0dd9b0581 --- /dev/null +++ b/spec/core_functions/selector/replace/error.hrx @@ -0,0 +1,228 @@ +<===> wrong_name/input.scss +@use "sass:selector"; +a {b: selector.selector-replace(c, c, d)} + +<===> wrong_name/error +Error: Undefined function. + , +2 | a {b: selector.selector-replace(c, c, d)} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> selector/parent/input.scss +@use "sass:selector"; +a {b: selector.replace("&", "c", "d")} + +<===> selector/parent/error +Error: $selector: Parent selectors aren't allowed here. + , +1 | & + | ^ + ' + ,--> input.scss +2 | a {b: selector.replace("&", "c", "d")} + | =============================== invocation + ' + - 1:1 root stylesheet + +<===> +================================================================================ +<===> selector/invalid/input.scss +@use "sass:selector"; +a {b: selector.replace("[c", "d", "e")} + +<===> selector/invalid/error +Error: $selector: expected more input. + , +1 | [c + | ^ + ' + ,--> input.scss +2 | a {b: selector.replace("[c", "d", "e")} + | ================================ invocation + ' + - 1:3 root stylesheet + +<===> +================================================================================ +<===> selector/type/input.scss +@use "sass:selector"; +a {b: selector.replace(1, "c", "d")} + +<===> selector/type/error +Error: $selector: 1 is not a valid selector: it must be a string, +a list of strings, or a list of lists of strings. + , +2 | a {b: selector.replace(1, "c", "d")} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> extendee/parent/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "&", "d")} + +<===> extendee/parent/error +Error: $original: Parent selectors aren't allowed here. + , +1 | & + | ^ + ' + ,--> input.scss +2 | a {b: selector.replace("c", "&", "d")} + | =============================== invocation + ' + - 1:1 root stylesheet + +<===> +================================================================================ +<===> extendee/complex/string/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d e", "f")} + +<===> extendee/complex/string/error +Error: Can't extend complex selector d e. + , +2 | a {b: selector.replace("c", "d e", "f")} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> extendee/complex/list/input.scss +@use "sass:selector"; +a {b: selector.replace("c", d e, "f")} + +<===> extendee/complex/list/error +Error: Can't extend complex selector d e. + , +2 | a {b: selector.replace("c", d e, "f")} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> extendee/invalid/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "[d", "e")} + +<===> extendee/invalid/error +Error: $original: expected more input. + , +1 | [d + | ^ + ' + ,--> input.scss +2 | a {b: selector.replace("c", "[d", "e")} + | ================================ invocation + ' + - 1:3 root stylesheet + +<===> +================================================================================ +<===> extendee/type/input.scss +@use "sass:selector"; +a {b: selector.replace("c", 1, "d")} + +<===> extendee/type/error +Error: $original: 1 is not a valid selector: it must be a string, +a list of strings, or a list of lists of strings. + , +2 | a {b: selector.replace("c", 1, "d")} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> extender/parent/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d", "&")} + +<===> extender/parent/error +Error: $replacement: Parent selectors aren't allowed here. + , +1 | & + | ^ + ' + ,--> input.scss +2 | a {b: selector.replace("c", "d", "&")} + | =============================== invocation + ' + - 1:1 root stylesheet + +<===> +================================================================================ +<===> extender/invalid/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d", "[e")} + +<===> extender/invalid/error +Error: $replacement: expected more input. + , +1 | [e + | ^ + ' + ,--> input.scss +2 | a {b: selector.replace("c", "d", "[e")} + | ================================ invocation + ' + - 1:3 root stylesheet + +<===> +================================================================================ +<===> extender/type/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d", 1)} + +<===> extender/type/error +Error: $replacement: 1 is not a valid selector: it must be a string, +a list of strings, or a list of lists of strings. + , +2 | a {b: selector.replace("c", "d", 1)} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> too_many_args/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d", "e", "f")} + +<===> too_many_args/error +Error: Only 3 arguments allowed, but 4 were passed. + ,--> input.scss +2 | a {b: selector.replace("c", "d", "e", "f")} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invocation + ' + ,--> sass:selector +1 | @function replace($selector, $original, $replacement) { + | =========================================== declaration + ' + input.scss 2:7 root stylesheet + +<===> +================================================================================ +<===> too_few_args/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d")} + +<===> too_few_args/error +Error: Missing argument $replacement. + ,--> input.scss +2 | a {b: selector.replace("c", "d")} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ invocation + ' + ,--> sass:selector +1 | @function replace($selector, $original, $replacement) { + | =========================================== declaration + ' + input.scss 2:7 root stylesheet diff --git a/spec/core_functions/selector/replace/format.hrx b/spec/core_functions/selector/replace/format.hrx new file mode 100644 index 0000000000..2167dab3c8 --- /dev/null +++ b/spec/core_functions/selector/replace/format.hrx @@ -0,0 +1,87 @@ +<===> input/non_string/README.md +These specs verify that all the arguments to `selector-extend()` can take the +parsed selector format, and that the function returns a selector in that format. +The full set of possible input formats is tested with `selector-parse()`; this +spec just verifies one example for each parameter. + +<===> +================================================================================ +<===> input/non_string/selector/input.scss +@use "sass:selector"; +a {b: selector.replace((c, d c), "c", "e")} + +<===> input/non_string/selector/output.css +a { + b: e, d e; +} + +<===> +================================================================================ +<===> input/non_string/extendee/input.scss +@use "sass:selector"; +a {b: selector.replace("c.d", (c, ".d"), ".e")} + +<===> input/non_string/extendee/output.css +a { + b: .e; +} + +<===> +================================================================================ +<===> input/non_string/extender/input.scss +@use "sass:selector"; +a {b: selector.replace("c", "c", (d, e f))} + +<===> input/non_string/extender/output.css +a { + b: d, e f; +} + +<===> +================================================================================ +<===> input/multiple_extendees/compound/input.scss +@use "sass:selector"; +a {b: selector.replace("c.d", "c.d", ".e")} + +<===> input/multiple_extendees/compound/output.css +a { + b: .e; +} + +<===> +================================================================================ +<===> input/multiple_extendees/list/input.scss +@use "sass:selector"; +a {b: selector.replace("c.d", "c, .d", ".e")} + +<===> input/multiple_extendees/list/output.css +a { + b: .e; +} + +<===> +================================================================================ +<===> input/multiple_extendees/list_of_compound/input.scss +@use "sass:selector"; +a {b: selector.replace("c.d.e.f", "c.d, .e.f", ".g")} + +<===> input/multiple_extendees/list_of_compound/output.css +a { + b: .g; +} + +<===> +================================================================================ +<===> output/input.scss +@use "sass:selector"; +$result: selector.replace("c d, e f", "g", "g"); +a { + result: $result; + structure: $result == ("c" "d", "e" "f"); +} + +<===> output/output.css +a { + result: c d, e f; + structure: true; +} diff --git a/spec/core_functions/selector/replace/in_is.hrx b/spec/core_functions/selector/replace/in_is.hrx new file mode 100644 index 0000000000..7acf4d2390 --- /dev/null +++ b/spec/core_functions/selector/replace/in_is.hrx @@ -0,0 +1,173 @@ +<===> simple_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c)", ".c", ".d")} + +<===> simple_target/single_extender/output.css +a { + b: .d; +} + +<===> +================================================================================ +<===> simple_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c)", ".c", ".d.e")} + +<===> simple_target/compound_extender/output.css +a { + b: .d.e; +} + +<===> +================================================================================ +<===> simple_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c)", ".c", ".d .e")} + +<===> simple_target/complex_extender/output.css +a { + b: :is(.d .e); +} + +<===> +================================================================================ +<===> simple_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c)", ".c", ".d, .e")} + +<===> simple_target/list_extender/output.css +a { + b: :is(.d, .e); +} + +<===> +================================================================================ +<===> compound_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c.d)", ".c", ".e")} + +<===> compound_target/single_extender/output.css +a { + b: .e.d; +} + +<===> +================================================================================ +<===> compound_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c.d)", ".c", ".e.f")} + +<===> compound_target/compound_extender/output.css +a { + b: .e.f.d; +} + +<===> +================================================================================ +<===> compound_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c.d)", ".c", ".e .f")} + +<===> compound_target/complex_extender/output.css +a { + b: .d:is(.e .f); +} + +<===> +================================================================================ +<===> compound_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c.d)", ".c", ".e, .f")} + +<===> compound_target/list_extender/output.css +a { + b: .d:is(.e, .f); +} + +<===> +================================================================================ +<===> complex_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c .d)", ".d", ".e")} + +<===> complex_target/single_extender/output.css +a { + b: :is(.c .e); +} + +<===> +================================================================================ +<===> complex_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c .d)", ".d", ".e.f")} + +<===> complex_target/compound_extender/output.css +a { + b: :is(.c .e.f); +} + +<===> +================================================================================ +<===> complex_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c .d)", ".d", ".e .f")} + +<===> complex_target/complex_extender/output.css +a { + b: :is(.c :is(.e .f)); +} + +<===> +================================================================================ +<===> complex_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c .d)", ".d", ".e, .f")} + +<===> complex_target/list_extender/output.css +a { + b: :is(.c :is(.e, .f)); +} + +<===> +================================================================================ +<===> list_target/single_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c, .d)", ".d", ".e")} + +<===> list_target/single_extender/output.css +a { + b: :is(.c, .e); +} + +<===> +================================================================================ +<===> list_target/compound_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c, .d)", ".d", ".e.f")} + +<===> list_target/compound_extender/output.css +a { + b: :is(.c, .e.f); +} + +<===> +================================================================================ +<===> list_target/complex_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c, .d)", ".d", ".e .f")} + +<===> list_target/complex_extender/output.css +a { + b: :is(.c, .e .f); +} + +<===> +================================================================================ +<===> list_target/list_extender/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(.c, .d)", ".d", ".e, .f")} + +<===> list_target/list_extender/output.css +a { + b: :is(.c, .e, .f); +} diff --git a/spec/core_functions/selector/replace/named.hrx b/spec/core_functions/selector/replace/named.hrx new file mode 100644 index 0000000000..78ae1b7ea8 --- /dev/null +++ b/spec/core_functions/selector/replace/named.hrx @@ -0,0 +1,8 @@ +<===> input.scss +@use "sass:selector"; +a {b: selector.replace($selector: "c.d", $original: "c", $replacement: "e")} + +<===> output.css +a { + b: e.d; +} diff --git a/spec/core_functions/selector/replace/no_op.hrx b/spec/core_functions/selector/replace/no_op.hrx new file mode 100644 index 0000000000..65fdc9a311 --- /dev/null +++ b/spec/core_functions/selector/replace/no_op.hrx @@ -0,0 +1,8 @@ +<===> input.scss +@use "sass:selector"; +a {b: selector.replace("c", "d", "e")} + +<===> output.css +a { + b: c; +} diff --git a/spec/core_functions/selector/replace/partial_no_op.hrx b/spec/core_functions/selector/replace/partial_no_op.hrx new file mode 100644 index 0000000000..e3bb10599e --- /dev/null +++ b/spec/core_functions/selector/replace/partial_no_op.hrx @@ -0,0 +1,8 @@ +<===> input.scss +@use "sass:selector"; +a {b: selector.replace("c, d", "d", "e")} + +<===> output.css +a { + b: c, e; +} diff --git a/spec/core_functions/selector/replace/selector_pseudo.hrx b/spec/core_functions/selector/replace/selector_pseudo.hrx new file mode 100644 index 0000000000..06a39a30ca --- /dev/null +++ b/spec/core_functions/selector/replace/selector_pseudo.hrx @@ -0,0 +1,41 @@ +<===> is/input.scss +@use "sass:selector"; +a {b: selector.replace(":is(c)", "c", "d")} + +<===> is/output.css +a { + b: d; +} + +<===> +================================================================================ +<===> where/input.scss +@use "sass:selector"; +a {b: selector.replace(":where(c)", "c", "d")} + +<===> where/output.css +a { + b: :where(d); +} + +<===> +================================================================================ +<===> matches/input.scss +@use "sass:selector"; +a {b: selector.replace(":matches(c)", "c", "d")} + +<===> matches/output.css +a { + b: :matches(d); +} + +<===> +================================================================================ +<===> not/input.scss +@use "sass:selector"; +a {b: selector.replace(":not(c)", "c", "d")} + +<===> not/output.css +a { + b: :not(d); +} diff --git a/spec/core_functions/selector/replace/simple.hrx b/spec/core_functions/selector/replace/simple.hrx new file mode 100644 index 0000000000..4bcfa74b7c --- /dev/null +++ b/spec/core_functions/selector/replace/simple.hrx @@ -0,0 +1,8 @@ +<===> input.scss +@use "sass:selector"; +a {b: selector.replace("c", "c", "d")} + +<===> output.css +a { + b: d; +} diff --git a/spec/directives/extend/in_is/after_target.hrx b/spec/directives/extend/in_is/after_target.hrx new file mode 100644 index 0000000000..e2f23e146f --- /dev/null +++ b/spec/directives/extend/in_is/after_target.hrx @@ -0,0 +1,20 @@ +<===> multiple_recursive/input.scss +// Regression test for sass/dart-sass#1109. +:is(.a .b) { + c: d; +} + +.a.mod1, .a.mod2 { + @extend .a, .b; +} +.a.mod3, .a.mod4 { + @extend .a, .b; +} +.a.mod5, .a.mod6 { + @extend .a, .b; +} + +<===> multiple_recursive/output.css +:is(.a :is(.b, .a.mod5, .a.mod6, .mod3.a, .mod4.a, .mod1.a, .mod2.a)) { + c: d; +} diff --git a/spec/directives/extend/pseudo.hrx b/spec/directives/extend/in_is/pseudo.hrx similarity index 100% rename from spec/directives/extend/pseudo.hrx rename to spec/directives/extend/in_is/pseudo.hrx diff --git a/spec/directives/extend/in_is/trim.hrx b/spec/directives/extend/in_is/trim.hrx new file mode 100644 index 0000000000..86b9c8da05 --- /dev/null +++ b/spec/directives/extend/in_is/trim.hrx @@ -0,0 +1,192 @@ +<===> README.md +These are tested here rather than in the extend functions because they exercise +behavior relating to preserving the source specificity of various selectors +(which is not tracked by functions) in order to comply with the second law of +extend. + +<===> +================================================================================ +<===> trimmable/context/input.scss +// `:is(.b, .b.c)` can be reduced to `.b` because `.a` increases the total +// specificity to 020, which is the minimum required by `.b.c`. +:is(.a .b) {x: y} +.b.c {@extend .b} + +<===> trimmable/context/output.css +:is(.a .b) { + x: y; +} + +<===> +================================================================================ +<===> trimmable/sibling/input.scss +// `:is(.c, .c.d)` can be reduced to `.c` because `.a.b` increases the total +// specificity to 020, which is the minimum required by `.c.d`. +:is(.a.b, .c) {x: y} +.c.d {@extend .c} + +<===> trimmable/sibling/output.css +:is(.a.b, .c) { + x: y; +} + +<===> +================================================================================ +<===> trimmable/where/parent/single/input.scss +:where(.a) {x: y} +.a.b {@extend .a} + +<===> trimmable/where/parent/single/output.css +:where(.a) { + x: y; +} + +<===> +================================================================================ +<===> trimmable/where/parent/sibling/input.scss +:where(.a, .b) {x: y} +.b.c {@extend .b} + +<===> trimmable/where/parent/sibling/output.css +:where(.a, .b) { + x: y; +} + +<===> +================================================================================ +<===> trimmable/where/grandparent/single/input.scss +:where(:is(.a)) {x: y} +.a.b {@extend .a} + +<===> trimmable/where/grandparent/single/output.css +:where(.a) { + x: y; +} + +<===> +================================================================================ +<===> trimmable/where/grandparent/sibling/input.scss +:where(:is(.a, .b)) {x: y} +.b.c {@extend .b} + +<===> trimmable/where/grandparent/sibling/output.css +:where(.a, .b) { + x: y; +} + +<===> +================================================================================ +<===> trimmable/super_selector_with_combinator/input.scss +a b { + @extend %c; +} + +a > b { + @extend %c; +} + +:is(%c) { + color: red; +} + +<===> trimmable/super_selector_with_combinator/output.css +:is(a b) { + color: red; +} + +<===> +================================================================================ +<===> untrimmable/single/input.scss +:is(.a) {x: y} +.a.b {@extend .a} + +<===> untrimmable/single/output.css +:is(.a, .a.b) { + x: y; +} + +<===> +================================================================================ +<===> untrimmable/sibling/input.scss +:is(.a, .b) {x: y} +.b.c {@extend .b} + +<===> untrimmable/sibling/output.css +:is(.a, .b, .b.c) { + x: y; +} + +<===> +================================================================================ +<===> untrimmable/preserves_source_specificity/input.scss +// This will result in :is(.a, :is(.a:(.b, .a.b))), which will be trimmed down +// to :is(.a, .a.b) +:is(.a, :is(.a.b)) {x: y} +.a.b {@extend .b} + +<===> untrimmable/preserves_source_specificity/output.css +:is(.a, .a.b) { + x: y; +} + +<===> +================================================================================ +<===> multiple_options/is/top_level/input.scss +// This generates :is(.a, .a.b., .a.c) before trimming. One of the two latter +// selectors can be trimmed without violating the second law of extend, but not +// both, since that would reduce the specificity to 010. +:is(.a) {x: y} +.a.b {@extend .a} +.a.c {@extend .a} + +<===> multiple_options/is/top_level/output.css +:is(.a, .a.c) { + x: y; +} + +<===> +================================================================================ +<===> multiple_options/is/nested/trimmable/input.scss +:is(:is(.a), .b.c) {x: y} +.a.d {@extend .a} +.a.e {@extend .a} + +<===> multiple_options/is/nested/trimmable/output.css +:is(.a, .b.c) { + x: y; +} + +<===> +================================================================================ +<===> multiple_options/is/nested/untrimmable/input.scss +:is(:is(.a), .b) {x: y} +.a.c {@extend .a} +.a.d {@extend .a} + +<===> multiple_options/is/nested/untrimmable/output.css +:is(.a, .a.d, .b) { + x: y; +} + +<===> +================================================================================ +<===> multiple_options/where/input.scss +:where(.a) {x: y} +.a.b {@extend .a} +.a.c {@extend .a} + +<===> multiple_options/where/output.css +:where(.a) { + x: y; +} + +<===> +================================================================================ +<===> identical/input.scss +:is(.a) {x: y} +.b, .b {@extend .a} + +<===> identical/output.css +:is(.a, .b) { + x: y; +} diff --git a/spec/directives/extend/after_target.hrx b/spec/directives/extend/top_level/after_target.hrx similarity index 100% rename from spec/directives/extend/after_target.hrx rename to spec/directives/extend/top_level/after_target.hrx diff --git a/spec/directives/extend/trims_super_selector_without_combinator.hrx b/spec/directives/extend/top_level/trims_super_selector_without_combinator.hrx similarity index 100% rename from spec/directives/extend/trims_super_selector_without_combinator.hrx rename to spec/directives/extend/top_level/trims_super_selector_without_combinator.hrx diff --git a/spec/libsass-closed-issues/issue_2055.hrx b/spec/libsass-closed-issues/issue_2055.hrx index b58399b865..ffa352d069 100644 --- a/spec/libsass-closed-issues/issue_2055.hrx +++ b/spec/libsass-closed-issues/issue_2055.hrx @@ -12,14 +12,14 @@ } <===> output.css -:not(.thing):not(:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))) { +:not(.thing):not(:not(.thing[disabled]):not([disabled]:has(:not([disabled]:is(.thing, :not(.thing[disabled])))))) { color: red; } -:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))):not([disabled]:has(:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))):not([disabled]:has(:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))))))) { +:not(.thing[disabled]):not([disabled]:has(:not([disabled]:is(.thing, :not(.thing[disabled]))))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not([disabled]:is(.thing, :not(.thing[disabled])))))):not([disabled]:has(:not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled])))), :not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled])))))))))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not([disabled]:is(.thing, :not(.thing[disabled]))))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not([disabled]:is(.thing, :not(.thing[disabled])))))):not([disabled]:has(:not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled])))), :not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled]))))))))))) { background: blue; } -:has(:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))):not([disabled]:has(:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))):not([disabled]:has(:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled])))):not([disabled]:not(.thing[disabled]):not([disabled]:has(:not(.thing[disabled]):not([disabled]:not(.thing[disabled]))))))))) { +:has(:not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled])))), :not(.thing[disabled]):not([disabled]:has(:not([disabled]:is(.thing, :not(.thing[disabled]))))), :has(:not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled])))), :not([disabled]:is(.thing, :has(:not([disabled]:is(.thing, :not(.thing[disabled])))))))))))) { background: blue; } From 99024fe71d8cec9916560578554d3d01bb84ced9 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 22 Sep 2025 14:07:07 -0700 Subject: [PATCH 2/2] Code review --- .../selector/extend/{in_is => modern_pseudo}/has.hrx | 0 .../selector/extend/{in_is => modern_pseudo}/is.hrx | 0 .../selector/extend/{in_is => modern_pseudo}/where.hrx | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename spec/core_functions/selector/extend/{in_is => modern_pseudo}/has.hrx (100%) rename spec/core_functions/selector/extend/{in_is => modern_pseudo}/is.hrx (100%) rename spec/core_functions/selector/extend/{in_is => modern_pseudo}/where.hrx (100%) diff --git a/spec/core_functions/selector/extend/in_is/has.hrx b/spec/core_functions/selector/extend/modern_pseudo/has.hrx similarity index 100% rename from spec/core_functions/selector/extend/in_is/has.hrx rename to spec/core_functions/selector/extend/modern_pseudo/has.hrx diff --git a/spec/core_functions/selector/extend/in_is/is.hrx b/spec/core_functions/selector/extend/modern_pseudo/is.hrx similarity index 100% rename from spec/core_functions/selector/extend/in_is/is.hrx rename to spec/core_functions/selector/extend/modern_pseudo/is.hrx diff --git a/spec/core_functions/selector/extend/in_is/where.hrx b/spec/core_functions/selector/extend/modern_pseudo/where.hrx similarity index 100% rename from spec/core_functions/selector/extend/in_is/where.hrx rename to spec/core_functions/selector/extend/modern_pseudo/where.hrx