diff --git a/.gitmodules b/.gitmodules index 217dfdb9..e34c4c5e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,6 @@ [submodule "Sources/AspxParser"] path = Sources/AspxParser url = https://github.com/PositiveTechnologies/AspxParser.git - branch = master [submodule "Sources/antlr-grammars-v4"] path = Sources/antlr-grammars-v4 url = https://github.com/PositiveTechnologies/grammars-v4.git - branch = master diff --git a/Sources/PT.PM.CSharpParseTreeUst/Converter/ExpressionVisitor.cs b/Sources/PT.PM.CSharpParseTreeUst/Converter/ExpressionVisitor.cs index 97443108..743320b3 100644 --- a/Sources/PT.PM.CSharpParseTreeUst/Converter/ExpressionVisitor.cs +++ b/Sources/PT.PM.CSharpParseTreeUst/Converter/ExpressionVisitor.cs @@ -12,7 +12,6 @@ using PT.PM.Common.Exceptions; using System.Collections.Generic; using PT.PM.Common.Nodes.Tokens.Literals; -//using Microsoft.CodeAnalysis.FindSymbols; namespace PT.PM.CSharpParseTreeUst.RoslynUstVisitor { diff --git a/Sources/PT.PM.CSharpParseTreeUst/Converter/StatementVisitor.cs b/Sources/PT.PM.CSharpParseTreeUst/Converter/StatementVisitor.cs index 266ceed7..ec0618f3 100644 --- a/Sources/PT.PM.CSharpParseTreeUst/Converter/StatementVisitor.cs +++ b/Sources/PT.PM.CSharpParseTreeUst/Converter/StatementVisitor.cs @@ -28,8 +28,8 @@ public override Ust VisitLocalDeclarationStatement(LocalDeclarationStatementSynt node.Declaration.Variables.Select(v => (AssignmentExpression)VisitAndReturnNullIfError(v)) .ToArray(); - var resultExpression = new VariableDeclarationExpression(type, variables, node.GetTextSpan()); - var result = new ExpressionStatement(resultExpression); + var resultExpression = new VariableDeclarationExpression(type, variables, node.Declaration.GetTextSpan()); + var result = new ExpressionStatement(resultExpression, node.GetTextSpan()); return result; } diff --git a/Sources/PT.PM.Cli/CliParameters.cs b/Sources/PT.PM.Cli/CliParameters.cs index 3149f7b4..f64fbeac 100644 --- a/Sources/PT.PM.Cli/CliParameters.cs +++ b/Sources/PT.PM.Cli/CliParameters.cs @@ -9,10 +9,10 @@ public class CliParameters [Option('f', "files", HelpText = "Input file or directory to be processed")] public string InputFileNameOrDirectory { get; set; } = ""; - [Option('l', "languages", HelpText = "Languages to be processed")] + [Option('l', "languages", HelpText = "Languages to be processed (at least -f or -p parameter required)")] public string Languages { get; set; } = ""; - [Option('p', "patterns", HelpText = "Patterns to be processed (json or base64 encoded)")] + [Option('p', "patterns", HelpText = "Patterns to be processed (at least -f or -p parameter required)")] public string Patterns { get; set; } = ""; [Option('t', "threads", HelpText = "Number of processing threads")] @@ -57,9 +57,6 @@ public class CliParameters [Option('d', "dump", HelpText = "Stages to be dumped (ParseTree, Ust)")] public string DumpStages { get; set; } = ""; - [Option('v', "version", HelpText = "Show version or not")] - public bool ShowVersion { get; set; } = true; - [Option('r', "render", HelpText = "Stages to be rendered")] public string RenderStages { get; set; } = ""; diff --git a/Sources/PT.PM.Cli/CliProcessorBase.cs b/Sources/PT.PM.Cli/CliProcessorBase.cs index fe609fc2..46df6e5f 100644 --- a/Sources/PT.PM.Cli/CliProcessorBase.cs +++ b/Sources/PT.PM.Cli/CliProcessorBase.cs @@ -56,12 +56,9 @@ public int Convert(string[] args, CliParameters parameters) try { - if (parameters.ShowVersion) - { - AssemblyName assemblyName = Assembly.GetEntryAssembly().GetName(); - string name = assemblyName.Name.Replace(".Cli", ""); - logger.LogInfo($"{name} version: {assemblyName.Version}"); - } + AssemblyName assemblyName = Assembly.GetEntryAssembly().GetName(); + string name = assemblyName.Name.Replace(".Cli", ""); + logger.LogInfo($"{name} version: {assemblyName.Version}"); if (logger is FileLogger abstractLogger) { @@ -76,7 +73,7 @@ public int Convert(string[] args, CliParameters parameters) if (string.IsNullOrEmpty(parameters.InputFileNameOrDirectory) && string.IsNullOrEmpty(parameters.Patterns)) { - throw new ArgumentException("at least --files or --patterns parameter required"); + throw new ArgumentException("at least -f or -p parameter required"); } if (!Enum.TryParse(parameters.Stage, true, out Stage pmStage)) diff --git a/Sources/PT.PM.Common/Json/JsonBaseSerializer.cs b/Sources/PT.PM.Common/Json/JsonBaseSerializer.cs index 1a077370..b7edc5b3 100644 --- a/Sources/PT.PM.Common/Json/JsonBaseSerializer.cs +++ b/Sources/PT.PM.Common/Json/JsonBaseSerializer.cs @@ -1,8 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using PT.PM.Common.Nodes; using System.Collections.Generic; -using System.Linq; namespace PT.PM.Common.Json { diff --git a/Sources/PT.PM.Common/Json/JsonConverterBase.cs b/Sources/PT.PM.Common/Json/JsonConverterBase.cs index a90c6e12..ebdc6703 100644 --- a/Sources/PT.PM.Common/Json/JsonConverterBase.cs +++ b/Sources/PT.PM.Common/Json/JsonConverterBase.cs @@ -3,6 +3,7 @@ using PT.PM.Common.Nodes; using PT.PM.Common.Reflection; using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -10,8 +11,12 @@ namespace PT.PM.Common.Json { public abstract class JsonConverterBase : JsonConverter, ILoggable { + private MultiMap existingUsts = new MultiMap(); + protected const string KindName = "Kind"; + protected JsonSerializer jsonSerializer; + public ILogger Logger { get; set; } = DummyLogger.Instance; public CodeFile JsonFile { get; } = CodeFile.Empty; @@ -100,7 +105,7 @@ protected JObject GetJObject(object value, JsonSerializer serializer) return jObject; } - protected Ust CreateUst(object jObjectOrToken) + protected Ust CreateOrGetUst(object jObjectOrToken) { JObject jObject = jObjectOrToken as JObject; JToken jToken = jObject == null ? jObjectOrToken as JToken : null; @@ -121,6 +126,26 @@ protected Ust CreateUst(object jObjectOrToken) return null; } + JToken textSpanToken = jObject != null + ? jObject.GetValueIgnoreCase(nameof(Ust.TextSpan)) + : jToken != null + ? jToken.GetValueIgnoreCase(nameof(Ust.TextSpan)) + : null; + + TextSpan textSpan = TextSpan.Empty; + if (textSpanToken != null) + { + textSpan = textSpanToken.ToObject(jsonSerializer); + if (!textSpan.IsEmpty && existingUsts.TryGetValue(textSpan, out List usts)) + { + var sameTypeUst = usts.FirstOrDefault(u => u.GetType() == type); + if (sameTypeUst != null) + { + return sameTypeUst; + } + } + } + Ust ust; if (type == typeof(RootUst)) { @@ -141,6 +166,12 @@ protected Ust CreateUst(object jObjectOrToken) ust = (Ust)Activator.CreateInstance(type); } + if (!textSpan.IsEmpty) + { + ust.TextSpan = textSpan; + existingUsts.Add(textSpan, ust); + } + return ust; } diff --git a/Sources/PT.PM.Common/Json/UstJsonConverter.cs b/Sources/PT.PM.Common/Json/UstJsonConverter.cs index 9b14f562..72a9d2e2 100644 --- a/Sources/PT.PM.Common/Json/UstJsonConverter.cs +++ b/Sources/PT.PM.Common/Json/UstJsonConverter.cs @@ -21,6 +21,8 @@ public override bool CanConvert(Type objectType) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + jsonSerializer = serializer; + if (reader.TokenType == JsonToken.Null) { return null; @@ -35,7 +37,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { // Try load from Ust subfield. JToken jToken = jObject.GetValueIgnoreCase(nameof(Ust)); - target = CreateUst(jToken); + target = CreateOrGetUst(jToken); if (target == null) { return null; @@ -44,7 +46,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist } else { - target = CreateUst(jObject); + target = CreateOrGetUst(jObject); if (target == null) { return null; diff --git a/Sources/PT.PM.Common/MultiMap.cs b/Sources/PT.PM.Common/MultiMap.cs new file mode 100644 index 00000000..f8783ea7 --- /dev/null +++ b/Sources/PT.PM.Common/MultiMap.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace PT.PM.Common +{ + /// + /// An alternative to .NET Dictionary class that allows duplicate keys, + /// and is still mutable (unlike the Lookup class). + /// + /// + /// Inspired by http://stackoverflow.com/questions/146204/duplicate-keys-in-net-dictionaries + /// + public class MultiMap + { + private readonly Dictionary> storage = new Dictionary>(); + + public IEnumerable Keys => storage.Keys; + + public int Count => storage.Count; + + public MultiMap() + { + } + + public MultiMap(IEnumerable> data) + { + foreach (KeyValuePair pair in data) + { + Add(pair.Key, pair.Value); + } + } + + public void Add(TKey key, TValue value) + { + if (!storage.TryGetValue(key, out List storageValue)) + { + storageValue = new List(); + storage[key] = storageValue; + } + storageValue.Add(value); + } + + public void Clear() + { + storage.Clear(); + } + + public bool ContainsKey(TKey key) + { + return storage.ContainsKey(key); + } + + public bool TryGetValue(TKey key, out List value) => storage.TryGetValue(key, out value); + + public List this[TKey key] => + storage.TryGetValue(key, out List storageValue) + ? storageValue + : new List(); + } +} diff --git a/Sources/PT.PM.Common/Nodes/Expressions/AssignmentExpression.cs b/Sources/PT.PM.Common/Nodes/Expressions/AssignmentExpression.cs index c87c55f8..20435e5a 100644 --- a/Sources/PT.PM.Common/Nodes/Expressions/AssignmentExpression.cs +++ b/Sources/PT.PM.Common/Nodes/Expressions/AssignmentExpression.cs @@ -23,7 +23,11 @@ public AssignmentExpression() public override string ToString() { - return Right == null ? Left.ToString() : $"{Left} = {Right}"; + return Right == null + ? Left == null + ? " = " + : Left.ToString() + : $"{Left} = {Right}"; } } } diff --git a/Sources/PT.PM.Common/Nodes/Expressions/VariableDeclarationExpression.cs b/Sources/PT.PM.Common/Nodes/Expressions/VariableDeclarationExpression.cs index 96159a43..0059e3ba 100644 --- a/Sources/PT.PM.Common/Nodes/Expressions/VariableDeclarationExpression.cs +++ b/Sources/PT.PM.Common/Nodes/Expressions/VariableDeclarationExpression.cs @@ -11,7 +11,7 @@ public class VariableDeclarationExpression : Expression public List Variables { get; set; } public VariableDeclarationExpression(TypeToken type, IEnumerable variables, - TextSpan textSpan) + TextSpan textSpan = default(TextSpan)) : base(textSpan) { Type = type; diff --git a/Sources/PT.PM.Common/Nodes/GeneralScope/TypeDeclaration.cs b/Sources/PT.PM.Common/Nodes/GeneralScope/TypeDeclaration.cs index b550952f..a3353556 100644 --- a/Sources/PT.PM.Common/Nodes/GeneralScope/TypeDeclaration.cs +++ b/Sources/PT.PM.Common/Nodes/GeneralScope/TypeDeclaration.cs @@ -34,6 +34,8 @@ public TypeDeclaration(TypeTypeLiteral type, IdToken name, List baseT public TypeDeclaration() { + BaseTypes = new List(); + TypeMembers = new List(); } public override Ust[] GetChildren() diff --git a/Sources/PT.PM.Common/Nodes/Tokens/Literals/StringLiteral.cs b/Sources/PT.PM.Common/Nodes/Tokens/Literals/StringLiteral.cs index de9345e3..360057ff 100644 --- a/Sources/PT.PM.Common/Nodes/Tokens/Literals/StringLiteral.cs +++ b/Sources/PT.PM.Common/Nodes/Tokens/Literals/StringLiteral.cs @@ -1,4 +1,7 @@ -using System; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; namespace PT.PM.Common.Nodes.Tokens.Literals { @@ -38,4 +41,19 @@ public override int CompareTo(Ust other) public override string ToString() => $"\"{Text}\""; } + + public static class InitialTextSpanPopulate + { + public static void Populate(this List textSpans, StringLiteral stringLiteral) + { + if(stringLiteral.InitialTextSpans.Any()) + { + textSpans.AddRange(stringLiteral.InitialTextSpans); + } + else + { + textSpans.Add(stringLiteral.TextSpan); + } + } + } } \ No newline at end of file diff --git a/Sources/PT.PM.Common/Nodes/Ust.cs b/Sources/PT.PM.Common/Nodes/Ust.cs index 82cac2aa..6555b08f 100644 --- a/Sources/PT.PM.Common/Nodes/Ust.cs +++ b/Sources/PT.PM.Common/Nodes/Ust.cs @@ -1,5 +1,7 @@ -using System; +using Newtonsoft.Json; +using System; using System.Diagnostics; +using System.Collections.Generic; using System.Text; namespace PT.PM.Common.Nodes @@ -29,6 +31,14 @@ public abstract class Ust : IComparable, IEquatable, IUst debuggerPrinter.Print(ToString()); + /// + /// The list of text spans before any UST transformation or reduction. + /// For example it won't be empty for concatenation of several strings, + /// i.e. "1" + "2" + "3" -> "123". + /// These spans map on an original source code. + /// + public List InitialTextSpans { get; set; } = new List(); + protected Ust() { } diff --git a/Sources/PT.PM.Common/Nodes/UstUtils.cs b/Sources/PT.PM.Common/Nodes/UstUtils.cs index 8c66f5e5..56f9599d 100644 --- a/Sources/PT.PM.Common/Nodes/UstUtils.cs +++ b/Sources/PT.PM.Common/Nodes/UstUtils.cs @@ -7,18 +7,18 @@ namespace PT.PM.Common.Nodes { public static class UstUtils { - public static Statement ToStatementIfRequired(this Ust node) + public static Statement ToStatementIfRequired(this Ust ust) { - Statement result = node as Statement; + Statement result = ust as Statement; if (result == null) { - if (node is Expression expr) + if (ust is Expression expr) { result = new ExpressionStatement(expr); } - else if (node != null) + else if (ust != null) { - result = new WrapperStatement(node); + result = new WrapperStatement(ust); } else { @@ -28,43 +28,49 @@ public static Statement ToStatementIfRequired(this Ust node) return result; } - public static Expression ToExpressionIfRequired(this Ust node) + public static Expression ToExpressionIfRequired(this Ust ust) { - if (node == null) + if (ust == null) { return null; } - Expression result = node as Expression; + Expression result = ust as Expression; if (result == null) { - result = new WrapperExpression(node); + result = new WrapperExpression(ust); } return result; } - public static BlockStatement ToBlockStatementIfRequired(this Statement statement) + public static bool IsInsideSingleBlockStatement(this Ust ust) { - if (statement is BlockStatement blockStatement) + Ust parent = ust; + while (parent != null && !(parent is Statement)) { - return blockStatement; + parent = parent.Parent; } - else + + if (parent == null) { - return new BlockStatement(new Statement[] { statement }); + return false; } + + Statement statement = (Statement)parent; + + return statement.Parent is Statement parentStatement && !(parentStatement is BlockStatement); } - public static Ust[] SelectAnalyzedNodes(this Ust ustNode, Language language, HashSet analyzedLanguages) + public static Ust[] SelectAnalyzedNodes(this Ust ust, Language language, HashSet analyzedLanguages) { Ust[] result; if (analyzedLanguages.Contains(language)) { - result = new Ust[] { ustNode }; + result = new Ust[] { ust }; } else { - result = ustNode.WhereDescendants( + result = ust.WhereDescendants( node => node is RootUst rootUst && analyzedLanguages.Contains(rootUst.Language)) .Cast() .ToArray(); @@ -72,22 +78,22 @@ public static Ust[] SelectAnalyzedNodes(this Ust ustNode, Language language, Has return result; } - public static void FillAscendants(this Ust ustNode) + public static void FillAscendants(this Ust ust) { - if (ustNode == null) + if (ust == null) { return; } - FillAscendantsLocal(ustNode, ustNode as RootUst); + FillAscendantsLocal(ust, ust as RootUst); - void FillAscendantsLocal(Ust node, RootUst root) + void FillAscendantsLocal(Ust localUst, RootUst root) { - foreach (Ust child in node.Children) + foreach (Ust child in localUst.Children) { if (child != null) { - child.Parent = node; + child.Parent = localUst; child.Root = root; if (child is RootUst rootUstChild) { @@ -102,15 +108,15 @@ void FillAscendantsLocal(Ust node, RootUst root) } } - public static TextSpan GetTextSpan(this IEnumerable nodes) + public static TextSpan GetTextSpan(this IEnumerable usts) { - if (nodes.Count() == 0) + if (usts.Count() == 0) { return default(TextSpan); } else { - return nodes.First().TextSpan.Union(nodes.Last().TextSpan); + return usts.First().TextSpan.Union(usts.Last().TextSpan); } } diff --git a/Sources/PT.PM.Common/PT.PM.Common.csproj b/Sources/PT.PM.Common/PT.PM.Common.csproj index 533d4f15..41233cf8 100644 --- a/Sources/PT.PM.Common/PT.PM.Common.csproj +++ b/Sources/PT.PM.Common/PT.PM.Common.csproj @@ -51,6 +51,7 @@ + diff --git a/Sources/PT.PM.Common/Reflection/PropertyVisitor.cs b/Sources/PT.PM.Common/Reflection/PropertyVisitor.cs index b06508ff..1f9e4d58 100644 --- a/Sources/PT.PM.Common/Reflection/PropertyVisitor.cs +++ b/Sources/PT.PM.Common/Reflection/PropertyVisitor.cs @@ -49,10 +49,20 @@ public TOutput VisitProperties(TInput node, Func visit) else if (propType.GetInterfaces().Contains(typeof(IEnumerable))) { Type itemType = propType.GetGenericArguments()[0]; - var sourceCollection = (IEnumerable)prop.GetValue(node); IList destCollection = null; - if (sourceCollection != null) + var collection = prop.GetValue(node); + if (collection != null) { + IEnumerable sourceCollection; + if (collection is IEnumerable refCollection) + { + sourceCollection = refCollection; + } + else + { + sourceCollection = ((IEnumerable)collection).Cast(); + } + destCollection = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType)); foreach (object item in sourceCollection) { diff --git a/Sources/PT.PM.Matching.Tests/PatternMatchingTests.cs b/Sources/PT.PM.Matching.Tests/PatternMatchingTests.cs index 09760b8c..1cf99e1a 100644 --- a/Sources/PT.PM.Matching.Tests/PatternMatchingTests.cs +++ b/Sources/PT.PM.Matching.Tests/PatternMatchingTests.cs @@ -42,7 +42,10 @@ public void Init() "\n" + "$password2 = \"1234\";\n" + "if ($password2->Length > 0) { }\n" + - "test_call_5(1, $password2, 2);", + "test_call_5(1, $password2, 2);\n" + + "$concat = \"111\" .\n" + + " \"222222222\" . \n" + + " \"333\";", "samples.php" ); @@ -52,9 +55,9 @@ public void Init() }; } - [TestCase("#()", new [] { 0 })] - [TestCase("#(a)", new [] { 1 })] - [TestCase("#(#*)", new [] { 0, 1, 2, 3, 4 })] + [TestCase("#()", new[] { 0 })] + [TestCase("#(a)", new[] { 1 })] + [TestCase("#(#*)", new[] { 0, 1, 2, 3, 4 })] [TestCase("#(a, #*)", new[] { 1, 2 })] [TestCase("#(#*, a)", new[] { 1, 3 })] [TestCase("#(#*, a, #*)", new[] { 1, 2, 3, 4 })] @@ -153,6 +156,23 @@ public void Match_Comments_CorrectMatchingPosition() Assert.AreEqual(16, textSpan2.EndColumn); } + [Test] + public void Match_Reduce_CorrectMatchingPosition() + { + var processor = new DslProcessor(); + PatternRoot patternNode = processor.Deserialize(new CodeFile("<[ \"\\d+\" ]>") { IsPattern = true }); + patternsRepository.Add(patternsConverter.ConvertBack(new List() { patternNode })); + WorkflowResult workflowResult = workflow.Process(); + List matchResults = workflowResult.MatchResults.ToDto().ToList(); + patternsRepository.Clear(); + + LineColumnTextSpan textSpan = matchResults[1].LineColumnTextSpan; + Assert.AreEqual(14, textSpan.BeginLine); + Assert.AreEqual(12, textSpan.BeginColumn); + Assert.AreEqual(16, textSpan.EndLine); + Assert.AreEqual(7, textSpan.EndColumn); + } + [Test] public void Create_PatternWithWrongLanguage_ThrowsException() { diff --git a/Sources/PT.PM.Matching/PatternUtils.cs b/Sources/PT.PM.Matching/PatternUtils.cs index c4c2535a..4f179dff 100644 --- a/Sources/PT.PM.Matching/PatternUtils.cs +++ b/Sources/PT.PM.Matching/PatternUtils.cs @@ -14,7 +14,7 @@ public static TextSpan[] MatchRegex(this Regex patternRegex, string text, int es foreach (Match match in matches) { TextSpan textSpan = match.GetTextSpan(text, escapeCharsLength); - if (!textSpan.IsEmpty) + if (match.Success) result.Add(textSpan); } return result.ToArray(); diff --git a/Sources/PT.PM.Matching/Patterns/PatternStringRegexLiteral.cs b/Sources/PT.PM.Matching/Patterns/PatternStringRegexLiteral.cs index 7746d260..9724af69 100644 --- a/Sources/PT.PM.Matching/Patterns/PatternStringRegexLiteral.cs +++ b/Sources/PT.PM.Matching/Patterns/PatternStringRegexLiteral.cs @@ -31,8 +31,67 @@ public PatternStringRegexLiteral() public override MatchContext Match(StringLiteral stringLiteral, MatchContext context) { IEnumerable matches = StringRegex - .MatchRegex(stringLiteral.Text, stringLiteral.EscapeCharsLength) - .Select(location => location.AddOffset(stringLiteral.TextSpan.Start)); + .MatchRegex(stringLiteral.Text, stringLiteral.EscapeCharsLength); + + if (stringLiteral.InitialTextSpans.Any()) + { + List result = new List(); + var initialTextSpans = stringLiteral.InitialTextSpans.OrderBy(el => el).ToList(); + var escapeLength = stringLiteral.EscapeCharsLength; + + foreach (TextSpan location in matches) + { + int offset = 0; + int leftBound = 1; + int rightBound = + initialTextSpans[0].Length - 2 * escapeLength + 1; // - quotes length and then + 1 + TextSpan textSpan = TextSpan.Empty; + + // Check first initial textspan separately + if (location.Start < rightBound && location.End > rightBound) + { + textSpan = location; + } + + for (int i = 1; i < initialTextSpans.Count; i++) + { + var initTextSpan = initialTextSpans[i]; + var prevTextSpan = initialTextSpans[i - 1]; + leftBound += prevTextSpan.Length - 2 * escapeLength; + rightBound += initTextSpan.Length - 2 * escapeLength; + offset += initTextSpan.Start - prevTextSpan.End + 2 * escapeLength; + + if (location.Start < leftBound && location.End < leftBound) + { + break; + } + + if (location.Start >= leftBound && location.Start < rightBound) + { + textSpan = location.AddOffset(offset); + if (location.End <= rightBound) + { + result.Add(textSpan); + break; + } + } + + if (!textSpan.IsEmpty && location.End <= rightBound) + { + result.Add(new TextSpan(textSpan.Start, location.Length + offset)); + break; + } + } + + if (textSpan.IsEmpty) + { + result.Add(location); + } + } + matches = result; + } + + matches = matches.Select(location => location.AddOffset(stringLiteral.TextSpan.Start)); return matches.Count() > 0 ? context.AddMatches(matches) diff --git a/Sources/PT.PM.PatternEditor/ViewModels/MainWindowViewModel.cs b/Sources/PT.PM.PatternEditor/ViewModels/MainWindowViewModel.cs index f06e9c75..cf4d99ba 100644 --- a/Sources/PT.PM.PatternEditor/ViewModels/MainWindowViewModel.cs +++ b/Sources/PT.PM.PatternEditor/ViewModels/MainWindowViewModel.cs @@ -239,7 +239,7 @@ private void UpdateSourceCodeSelection(int index) } var textSpan = TextSpan.FromBounds(start, end); var lineColumnTextSpan = sourceCode.GetLineColumnTextSpan(textSpan); - SourceCodeTextBoxPosition = $"Range: {lineColumnTextSpan}"; + SourceCodeTextBoxPosition = $"Range: {lineColumnTextSpan}; LineColumn: {textSpan}"; } else { diff --git a/Sources/PT.PM.PhpParseTreeUst/PhpAntlrParseTreeConverter.cs b/Sources/PT.PM.PhpParseTreeUst/PhpAntlrParseTreeConverter.cs index ce114d3c..68c49b4d 100644 --- a/Sources/PT.PM.PhpParseTreeUst/PhpAntlrParseTreeConverter.cs +++ b/Sources/PT.PM.PhpParseTreeUst/PhpAntlrParseTreeConverter.cs @@ -1636,7 +1636,10 @@ public Ust VisitInterpolatedStringPart([NotNull] PhpParser.InterpolatedStringPar Expression result; if (context.StringPart() != null) { - result = new StringLiteral(context.StringPart().GetText(), context.GetTextSpan()); + // TextSpan should include quotes + var oldTextSpan = context.GetTextSpan(); + var textSpan = new TextSpan(oldTextSpan.Start - 1, oldTextSpan.Length + 2); + result = new StringLiteral(context.StringPart().GetText(), textSpan); } else { diff --git a/Sources/PT.PM.Tests/WorkflowTests.cs b/Sources/PT.PM.Tests/WorkflowTests.cs index 93de5762..cb126eaa 100644 --- a/Sources/PT.PM.Tests/WorkflowTests.cs +++ b/Sources/PT.PM.Tests/WorkflowTests.cs @@ -61,6 +61,7 @@ private static void CheckSerialization(bool lineColumnTextSpans = false, } // Deserialization + var logger = new LoggerMessageCounter(); SourceCodeRepository sourceCodeRepository = new MemoryCodeRepository(code, inputFileName + ".ust.json") { LoadJson = true @@ -71,10 +72,12 @@ private static void CheckSerialization(bool lineColumnTextSpans = false, IsAsyncPatternsConversion = false, IndentedDump = indented, DumpWithTextSpans = includeTextSpans, - LineColumnTextSpans = lineColumnTextSpans + LineColumnTextSpans = lineColumnTextSpans, + Logger = logger }; var newResult = newWorkflow.Process(); + Assert.AreEqual(0, logger.ErrorCount); Assert.GreaterOrEqual(newResult.MatchResults.Count, 1); MatchResult match = newResult.MatchResults.FirstOrDefault(); if (includeTextSpans) diff --git a/Sources/PT.PM/UstSimplifier.cs b/Sources/PT.PM/UstSimplifier.cs index ec0d6d47..a8d57db9 100644 --- a/Sources/PT.PM/UstSimplifier.cs +++ b/Sources/PT.PM/UstSimplifier.cs @@ -67,12 +67,17 @@ public override Ust Visit(BinaryOperatorExpression binaryOperatorExpression) { if (op.BinaryOperator == BinaryOperator.Plus) { + List initialTextSpans = new List(); + initialTextSpans.Populate(leftString); + initialTextSpans.Populate(rightString); + string resultText = leftString.Text + rightString.Text; result = new StringLiteral { Text = resultText, Root = binaryOperatorExpression.Root, - TextSpan = leftExpression.TextSpan.Union(rightExpression.TextSpan) + TextSpan = leftExpression.TextSpan.Union(rightExpression.TextSpan), + InitialTextSpans = new List(initialTextSpans) }; Logger.LogDebug($"Strings {binaryOperatorExpression} has been concatenated to \"{resultText}\" at {result.TextSpan}"); }