From e490a306248cf4830b7d27dbf1c60b07692ae327 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 15 Jun 2023 18:22:36 +0200 Subject: [PATCH] fix(Manupilation): Adding nested Tag renames parent closing element #338 --- .../Parsing/XmlParser.cs | 25 ++++++-- ...torBasicTests.SynchronizeStartAndEndTag.cs | 62 +++++++++++++++++++ .../Manipulator/ManipulatorBasicTests.cs | 2 +- .../Manipulator/Util/ManipulatorTestBase.cs | 4 +- tests/CompletionEngineTests/Scenario.cs | 45 ++++++++++++++ 5 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.SynchronizeStartAndEndTag.cs create mode 100644 tests/CompletionEngineTests/Scenario.cs diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/XmlParser.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/XmlParser.cs index dd72ca23..4b33b498 100644 --- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/XmlParser.cs +++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/XmlParser.cs @@ -18,9 +18,10 @@ public enum ParserState BeforeAttributeValue, AttributeValue, AfterAttributeValue, + Error, } - public ParserState State { get; set; } + public ParserState State { get; private set; } private readonly ReadOnlyMemory _data; private int _parserPos; @@ -99,6 +100,7 @@ private bool ParseChar() var i = _parserPos++; var span = _data.Span; var c = span[i]; + char lastChar; if (c == '<' && State == ParserState.None) { State = ParserState.StartElement; @@ -168,6 +170,10 @@ private bool ParseChar() _attributeNameStart = i; _attributeNameEnd = null; } + else if (State == ParserState.InsideElement && c == '<') + { + State = ParserState.Error; + } else if (State == ParserState.StartAttribute && (c == '=' || char.IsWhiteSpace(c))) { State = ParserState.BeforeAttributeValue; @@ -186,6 +192,11 @@ private bool ParseChar() { State = ParserState.InsideElement; } + else if (State == ParserState.Error && CheckPrev(i - 1, "<")) + { + State = ParserState.StartElement; + _parserPos--; + } return true; } @@ -198,7 +209,6 @@ private bool ParseChar() if (m.Success) return m.Value.Substring(1); return null; - } public string? FindParentAttributeValue(string attributeExpr, int startLevel = 0, int maxLevels = int.MaxValue) @@ -277,7 +287,11 @@ public static XmlParser Parse(ReadOnlyMemory data) public static XmlParser Parse(ReadOnlyMemory data, int start, int end) { - var rv = new XmlParser(data, start); + var rv = new XmlParser(data, 0); + while(rv.ParserPos < start) + { + rv.ParseChar(); + } for (var i = start; i < end; i++) { if (!rv.ParseChar()) @@ -303,7 +317,8 @@ public bool SeekClosingTag() // leave initial start element } - while (NestingLevel != 0) + var currentLevel = NestingLevel; + while (NestingLevel >= currentLevel) { if (!ParseChar()) { @@ -321,7 +336,7 @@ public bool SeekClosingTag() // find start of next element } - return true; + return NestingLevel < currentLevel; } public override string ToString() diff --git a/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.SynchronizeStartAndEndTag.cs b/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.SynchronizeStartAndEndTag.cs new file mode 100644 index 00000000..c1773180 --- /dev/null +++ b/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.SynchronizeStartAndEndTag.cs @@ -0,0 +1,62 @@ +using Xunit; + +namespace CompletionEngineTests.Manipulator; + +partial class ManipulatorBasicTests +{ + const string nestingRenameSource = """ + + + +"""; + + const string nestingRenameExpected = """ + + + +"""; + + const string scenario2Source = """ + + + +"""; + + const string scenario2Expected = """ + + + +"""; + + [Theory] + [Scenario("Adding nested Tag renames parent closing element GitHub #338 ", nestingRenameExpected, nestingRenameSource)] + [Scenario("Rename with invalid nested tag", scenario2Expected, scenario2Source)] + public void SynchronizeStartAndEndTag(Scenario scenario) + { + AssertInsertion((string)scenario.Agrument, "d", (string)scenario.Expected); + } +} diff --git a/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.cs b/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.cs index 49f05009..af5308ed 100644 --- a/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.cs +++ b/tests/CompletionEngineTests/Manipulator/ManipulatorBasicTests.cs @@ -3,7 +3,7 @@ namespace CompletionEngineTests.Manipulator { - public class ManipulatorBasicTests : ManipulatorTestBase + public partial class ManipulatorBasicTests : ManipulatorTestBase { [Fact] public void DoNotInsertToUnclosedTag() diff --git a/tests/CompletionEngineTests/Manipulator/Util/ManipulatorTestBase.cs b/tests/CompletionEngineTests/Manipulator/Util/ManipulatorTestBase.cs index bdae6ad4..066c4433 100644 --- a/tests/CompletionEngineTests/Manipulator/Util/ManipulatorTestBase.cs +++ b/tests/CompletionEngineTests/Manipulator/Util/ManipulatorTestBase.cs @@ -1,6 +1,4 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Avalonia.Ide.CompletionEngine; using Xunit; diff --git a/tests/CompletionEngineTests/Scenario.cs b/tests/CompletionEngineTests/Scenario.cs new file mode 100644 index 00000000..103317c5 --- /dev/null +++ b/tests/CompletionEngineTests/Scenario.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Reflection; +using Xunit.Sdk; + +namespace CompletionEngineTests; + +/// +/// Test Scenario +/// +/// +/// +/// +public record class Scenario(string Description, object Expected, object Agrument) +{ + public override string ToString() + { + return Description; + } +} + +/// +/// Provides a data source for a data theory, with the data coming from inline values. +/// +public sealed class ScenarioAttribute : DataAttribute +{ + readonly object[] data; + + /// + /// Initializes a new instance of the class. + /// + /// The description of test scenario + /// The expected value + /// The argument of pass to test method. + public ScenarioAttribute(string description, object expected, object agrument) + { + Scenario scenario = new(description, expected, agrument); + data = new object[] { scenario }; + } + + /// + public override IEnumerable GetData(MethodInfo testMethod) + { + yield return data; + } +}