Roslyn+T4+EnvDTE项目完全自动化(8) ——转换linq表达式

写代码最开始时,为了方便大多写linq method chain,随着业务发展,需要把linq method chain转换成LINQ-expression更方便。

resharper有下面3种,可以重构项目:

但没有转换成LINQ-expression的插件。我写了一个插件可以转换成LINQ-expression。

 

代码:

    [ExportMenuItem("Refactor/linq")]
    public class ConvertLinqExpression : MenuItemAction
    {
        public ConvertLinqExpression()
            : base("convert linq expression")
        {
        }
        public override async Task Execute()
        {
            var dte = Helpers.MainViewModel.VsViewModel.SelectedItem.DTE;
            var documentSyntaxNode = new DocumentSyntaxNode(dte);
            ExpressionSyntax listExpression = null;
            var array = documentSyntaxNode.Node.Root.GetParentsFromTextSpan<InvocationExpressionSyntax>(documentSyntaxNode.Span,
                node =>
                {
                    var kinds = new[]
                    {
                        SyntaxKind.Interpolation,
                        SyntaxKind.ExpressionStatement,
                    };
                    return !kinds.Contains(node.Kind());
                }).ToArray();
            if (array.Length == 0)
            {
                if (JustConvert(documentSyntaxNode)) return;
                throw new Exception("没找到 Linq method");
            }
            var buf = new List<(string name, InvocationExpressionSyntax syntax)>();
            var names = new List<string>()
            {
                nameof(Enumerable.Where),
                nameof(Enumerable.Select),

                nameof(Enumerable.SelectMany),
                nameof(Enumerable.GroupBy),
                nameof(Enumerable.OrderBy),
                nameof(Enumerable.OrderByDescending),
            };
            var firstNames = new[]
            {
                nameof(Enumerable.First),
                nameof(Enumerable.FirstOrDefault),
            };
            names.AddRange(firstNames);

            for (var i = 0; i < array.Length; i++)
            {
                var syntax = array[i];
                if (syntax.Expression is MemberAccessExpressionSyntax member)
                {
                    var name = member.Name.Identifier.ValueText;
                    if (listExpression == null && names.Contains(name))
                    {
                        listExpression = member.Expression;
                    }
                    if (!names.Contains(name) || syntax.ArgumentList.Arguments.Count != 1)
                    {
                        break;
                    }

                    buf.Add((name, syntax));
                    if (firstNames.Contains(name))
                    {
                        break;
                    }
                }
            }

            if (listExpression == null || buf.Count == 0)
            {
                if (JustConvert(documentSyntaxNode)) return;
                throw new Exception($@"没找到 Linq method chain:
{array.JoinNewLine()}");
            }

            var used = new List<string>();

            var sb = new StringBuilder();
            var space = GetSameLineSpace(listExpression);

            for (var i = 0; i < buf.Count; i++)
            {
                var syntax = buf[i];
                var argument = syntax.syntax.ArgumentList.Arguments.First();
                if (used.Count == 0)
                {
                    TryAdd(used, syntax.syntax.GetLambdaArgumentName() ?? "p");
                }

                if (i == 0)
                {
                    sb.AppendLine($"{space}from {used.Last()} in {listExpression}");
                }

                switch (syntax.name)
                {
                    case nameof(Enumerable.Select):
                        {
                            var expression = argument.Expression;
                            var value = expression is SimpleLambdaExpressionSyntax lambda
                                        ? $"{TryRename(lambda, used.LastOrDefault())}"
                                        : $"{expression}({used.Last()})";

                            if (i == buf.Count - 1)
                            {
                                sb.AppendLine($"{space}select {value}");
                            }
                            else
                            {
                                sb.AppendLine($"{space}let {GetNewParam(used, buf, i)} = {value}");
                            }
                            break;
                        }
                    case nameof(Enumerable.First):
                    case nameof(Enumerable.FirstOrDefault):
                    case nameof(Enumerable.Where):
                        {
                            var expression = argument.Expression;
                            var value = expression is SimpleLambdaExpressionSyntax lambda
                                ? $"{TryRename(lambda, used.LastOrDefault())}"
                                : $"{expression}({used.Last()})";

                            sb.AppendLine($"{space}where {value}");
                            if (i == buf.Count - 1)
                            {
                                sb.AppendLine($"{space}select {used.Last()}");
                            }
                            break;
                        }
                    case nameof(Enumerable.SelectMany):
                        {
                            var expression = argument.Expression;
                            switch (expression)
                            {
                                case SimpleLambdaExpressionSyntax lambda:
                                    sb.AppendLine($"{space}from {GetNewParam(used, buf, i)} in {TryRename(lambda, TryGetLast2Name(used))}");
                                    break;
                                default:
                                    sb.AppendLine($"{space}from {GetNewParam(used, buf, i)} in {expression}({TryGetLast2Name(used)})");
                                    break;
                            }

                            break;
                        }
                    case nameof(Enumerable.GroupBy):
                        {
                            var expression = argument.Expression;
                            switch (expression)
                            {
                                case SimpleLambdaExpressionSyntax lambda:
                                    if (i == buf.Count - 1)
                                    {
                                        sb.AppendLine($"{space}group {used.Last()} by {TryRename(lambda, used.LastOrDefault())}");
                                    }
                                    else
                                    {
                                        sb.AppendLine($"{space}group {used.Last()} by {TryRename(lambda, used.LastOrDefault())} into {GetNewParam(used, buf, i)}");
                                        //sb.AppendLine($"from {GetNewParam(used, buf, i)} in g");
                                    }
                                    break;
                                default:
                                    if (i == buf.Count - 1)
                                    {
                                        sb.AppendLine($"{space}group {used.Last()} by {expression}({used.Last()})");
                                    }
                                    else
                                    {
                                        sb.AppendLine($"{space}group {used.Last()} by {expression}({used.Last()}) into {GetNewParam(used, buf, i)}");
                                    }
                                    break;
                            }

                            break;
                        }
                    case nameof(Enumerable.OrderByDescending):
                        {
                            var expression = argument.Expression;
                            switch (expression)
                            {
                                case SimpleLambdaExpressionSyntax lambda:
                                    sb.AppendLine($"{space}orderby {TryRename(lambda, used.LastOrDefault())} descending");
                                    break;
                                default:
                                    sb.AppendLine($"{space}orderby {expression}({used.Last()}) descending");
                                    break;
                            }

                            break;
                        }
                    case nameof(Enumerable.OrderBy):
                        {
                            var expression = argument.Expression;
                            switch (expression)
                            {
                                case SimpleLambdaExpressionSyntax lambda:
                                    sb.AppendLine($"{space}orderby {TryRename(lambda, used.LastOrDefault())}");
                                    break;
                                default:
                                    sb.AppendLine($"{space}orderby {expression}({used.Last()})");
                                    break;
                            }

                            break;
                        }
                }
                if (i == buf.Count - 1
                    && syntax.name != nameof(Enumerable.Select)
                    && syntax.name != nameof(Enumerable.Where)
                    && !firstNames.Contains(syntax.name)
                    )
                {
                    sb.AppendLine($"{space}select {used.Last()}");
                }
            }

            var srcSyntax = buf.Last().syntax;

            var code = sb.TrimNewLine();
            var end = firstNames.Contains(buf.Last().name) ? $".{buf.Last().name}()" : null;
            var text = $@"(
{code}
{space}){end}";
            documentSyntaxNode.Save(documentSyntaxNode.Node.Root.GetText().InsertText(new TextChange(srcSyntax.Span, text)));
        }

        private static string TryGetLast2Name(List<string> used)
        {
            return used.Count < 2 ? used.LastOrDefault() : used[used.Count - 2];
        }

        private bool JustConvert(DocumentSyntaxNode documentSyntaxNode)
        {
            var syntax = (
                from p in documentSyntaxNode.Node.Root.DescendantNodes().OfType<ExpressionSyntax>()
                where p.Span.IntersectsWith(documentSyntaxNode.Span)
                let a = p is NameSyntax
                orderby a descending, p.SpanStart descending
                select p
                )
                .FirstOrDefault();
            if (syntax == null) return false;
            if (syntax.GetParent(false, null) is MemberAccessExpressionSyntax node && node.Name == syntax)
            {
                JustConvert(documentSyntaxNode, node.GetParentNearest());
            }
            else
            {
                JustConvert(documentSyntaxNode, syntax.GetParentNearest());
            }
            return true;

        }

        private void JustConvert(DocumentSyntaxNode documentSyntaxNode, SyntaxNode syntaxNode)
        {
            var s = GetSameLineSpace(syntaxNode);
            var v = $@"(
{s}from p in {syntaxNode}
{s}select p
{s})";
            documentSyntaxNode.Save(documentSyntaxNode.Node.Root.GetText()
                .InsertText(new TextChange(syntaxNode.Span, v)));
        }
        private void JustConvert(DocumentSyntaxNode documentSyntaxNode, string methodName, TextSpan textSpan,
            SyntaxNode syntaxNode,
            string whereCode)
        {
            var s = GetSameLineSpace(syntaxNode);
            string w = null;
            if (whereCode != null)
            {
                w = $@"
{s}where {whereCode}";
            }
            var v = $@"(
{s}from p in {syntaxNode}{w}
{s}select p
{s}).{methodName}()";
            documentSyntaxNode.Save(documentSyntaxNode.Node.Root.GetText()
                .InsertText(new TextChange(textSpan, v)));
        }

        private string GetSameLineSpace(SyntaxNode syntax)
        {
            var line = syntax.GetLocation().GetLineSpan().StartLinePosition.Line;
            SyntaxNode sameLine = null;
            foreach (var syntaxNode in syntax.GetParents<SyntaxNode>())
            {
                if (syntaxNode.GetLocation().GetLineSpan().StartLinePosition.Line == line)
                {
                    sameLine = syntaxNode;
                }
                else
                {
                    break;
                }
            }

            var trivia = sameLine?.GetLeadingTrivia().FirstOrDefault(p => p.IsKind(SyntaxKind.WhitespaceTrivia))
                .ToFullString();
            if (string.IsNullOrEmpty(trivia))
            {
                return new string('\t', 3);
            }
            return trivia + new string('\t', 1);
        }

        private string TryRename(SimpleLambdaExpressionSyntax lambda, string usedName)
        {
            var name = lambda.GetLambdaArgumentName();
            var body = lambda.ExpressionBody ?? throw new Exception("没有简单LambdaExpression");
            if (string.IsNullOrEmpty(usedName) || usedName == name)
            {
                return body.ToFullString();
            }
            var regex = $@"\b{Regex.Escape(name)}\b".GetRegex(true, false);
            return regex.Replace(body.ToFullString(), usedName);
        }

        private string GetNewParam(List<string> used, List<(string name, InvocationExpressionSyntax syntax)> buf, int start)
        {
            if (start < buf.Count - 1)
            {
                var argumentName = buf[start + 1].syntax.GetLambdaArgumentName();
                if (string.IsNullOrEmpty(argumentName) || used.Contains(argumentName))
                {
                    goto New;
                }

                TryAdd(used, argumentName);
                return argumentName;
            }
            New:
            for (int i = 0; ; i++)
            {
                var name = $"p{(i == 0 ? "" : i)}";
                if (TryAdd(used, name))
                {
                    return name;
                }
            }
        }

        private static bool TryAdd(List<string> used, string name)
        {
            if (!used.Contains(name))
            {
                used.Add(name);
                return true;
            }
            return false;
        }
    }

  

posted @ 2022-10-14 16:27  WalkingDie  阅读(65)  评论(0)    收藏  举报