Browse Source

Parse nesting selector.

Steven Kirk 3 years ago
parent
commit
a506737a0f

+ 23 - 7
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@@ -46,7 +46,7 @@ namespace Avalonia.Markup.Parsers
                 switch (state)
                 {
                     case State.Start:
-                        state = ParseStart(ref r);
+                        (state, syntax) = ParseStart(ref r);
                         break;
                     case State.Middle:
                         (state, syntax) = ParseMiddle(ref r, end);
@@ -93,27 +93,31 @@ namespace Avalonia.Markup.Parsers
             return selector;
         }
 
-        private static State ParseStart(ref CharacterReader r)
+        private static (State, ISyntax?) ParseStart(ref CharacterReader r)
         {
             r.SkipWhitespace();
             if (r.End)
             {
-                return State.End;
+                return (State.End, null);
             }
 
             if (r.TakeIf(':'))
             {
-                return State.Colon;
+                return (State.Colon, null);
             }
             else if (r.TakeIf('.'))
             {
-                return State.Class;
+                return (State.Class, null);
             }
             else if (r.TakeIf('#'))
             {
-                return State.Name;
+                return (State.Name, null);
+            }
+            else if (r.TakeIf('&'))
+            {
+                return (State.CanHaveType, new NestingSyntax());
             }
-            return State.TypeName;
+            return (State.TypeName, null);
         }
 
         private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end)
@@ -142,6 +146,10 @@ namespace Avalonia.Markup.Parsers
             {
                 return (State.Start, new CommaSyntax());
             }
+            else if (r.TakeIf('&'))
+            {
+                return (State.CanHaveType, new NestingSyntax());
+            }
             else if (end.HasValue && !r.End && r.Peek == end.Value)
             {
                 return (State.End, null);
@@ -635,5 +643,13 @@ namespace Avalonia.Markup.Parsers
                 return obj is CommaSyntax or;
             }
         }
+
+        public class NestingSyntax : ISyntax
+        {
+            public override bool Equals(object? obj)
+            {
+                return obj is NestingSyntax;
+            }
+        }
     }
 }

+ 139 - 0
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@@ -469,6 +469,145 @@ namespace Avalonia.Markup.UnitTests.Parsers
                 result);
         }
 
+        [Fact]
+        public void Nesting_Class()
+        {
+            var result = SelectorGrammar.Parse("&.foo");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.ClassSyntax { Class = "foo" },
+                },
+                result);
+        }
+
+        [Fact]
+        public void Nesting_Child_Class()
+        {
+            var result = SelectorGrammar.Parse("& > .foo");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.ChildSyntax { },
+                    new SelectorGrammar.ClassSyntax { Class = "foo" },
+                },
+                result);
+        }
+
+        [Fact]
+        public void Nesting_Descendant_Class()
+        {
+            var result = SelectorGrammar.Parse("& .foo");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.DescendantSyntax { },
+                    new SelectorGrammar.ClassSyntax { Class = "foo" },
+                },
+                result);
+        }
+
+        [Fact]
+        public void Nesting_Template_Class()
+        {
+            var result = SelectorGrammar.Parse("& /template/ .foo");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.TemplateSyntax { },
+                    new SelectorGrammar.ClassSyntax { Class = "foo" },
+                },
+                result);
+        }
+
+        [Fact]
+        public void OfType_Template_Nesting()
+        {
+            var result = SelectorGrammar.Parse("Button /template/ &");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
+                    new SelectorGrammar.TemplateSyntax { },
+                    new SelectorGrammar.NestingSyntax(),
+                },
+                result);
+        }
+
+        [Fact]
+        public void Nesting_Property()
+        {
+            var result = SelectorGrammar.Parse("&[Foo=bar]");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.PropertySyntax { Property = "Foo", Value = "bar" },
+                },
+                result);
+        }
+
+        [Fact]
+        public void Not_Nesting()
+        {
+            var result = SelectorGrammar.Parse(":not(&)");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NotSyntax
+                    {
+                        Argument = new[] { new SelectorGrammar.NestingSyntax() },
+                    }
+                },
+                result);
+        }
+
+
+        [Fact]
+        public void Nesting_NthChild()
+        {
+            var result = SelectorGrammar.Parse("&:nth-child(2n+1)");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.NthChildSyntax()
+                    {
+                        Step = 2,
+                        Offset = 1
+                    }
+                },
+                result);
+        }
+
+        [Fact]
+        public void Nesting_Comma_Nesting_Class()
+        {
+            var result = SelectorGrammar.Parse("&, &.foo");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.CommaSyntax(),
+                    new SelectorGrammar.NestingSyntax(),
+                    new SelectorGrammar.ClassSyntax { Class = "foo" },
+                },
+                result);
+        }
+
         [Fact]
         public void Namespace_Alone_Fails()
         {