Roslyn+T4+EnvDTE项目完全自动化(8) ——转换linq表达式
写代码最开始时,为了方便大多写linq method chain,随着业务发展,需要把linq method chain转换成LINQ-expression更方便。
resharper有下面3种,可以重构项目:
- convert linq to method chain
- convert linq to code
- Loop can be converted into LINQ-expression
但没有转换成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;
}
}

浙公网安备 33010602011771号