Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neuecc/cc1578e8ee85b3ddadcf to your computer and use it in GitHub Desktop.
Save neuecc/cc1578e8ee85b3ddadcf to your computer and use it in GitHub Desktop.
AddDataMemberWithOrderCodeRefactoringProvider
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Grani.RoslynTools
{
[ExportCodeRefactoringProvider(AddDataMemberWithOrderCodeRefactoringProvider.RefactoringId, LanguageNames.CSharp), Shared]
internal class AddDataMemberWithOrderCodeRefactoringProvider : CodeRefactoringProvider
{
public const string RefactoringId = "AddDataMemberWithOrder";
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
// 選択範囲に含まれているもの(DescendantNodes(context.Span))からプロパティだけ抜き出す
var properties = root.DescendantNodes(context.Span)
.OfType<PropertyDeclarationSyntax>()
.ToArray();
if (properties.Length == 0) return;
// コードアクションを作って登録
var action = CodeAction.Create("Add DataMember with Order", c => AddDataMemberWithOrder(context.Document, model, root, properties, c));
context.RegisterRefactoring(action);
}
static Task<Document> AddDataMemberWithOrder(Document document, SemanticModel model, SyntaxNode root, IEnumerable<PropertyDeclarationSyntax> properties, CancellationToken cancellationToken)
{
var order = 1;
var newRoot = root.ReplaceNodes(properties, (node, _) =>
{
var existingDataMember = node.AttributeLists
.Select(x => x.DescendantNodes().OfType<AttributeSyntax>().FirstOrDefault())
.Where(x => x != null)
.Where(x => model.GetTypeInfo(x).Type.Name == "DataMemberAttribute") // System.Runtime.Serialization.DataMemberとDataMemberを同じに扱うため
.FirstOrDefault();
if (existingDataMember == null)
{
// 新規追加
// String interpolationベンリ
var attr = SyntaxFactory.ParseCompilationUnit("[DataMember(Order = \{order++})]")
.DescendantNodes()
.OfType<AttributeListSyntax>()
.First()
.WithLeadingTrivia(node.GetLeadingTrivia()) // 先頭のインデント(や改行)を属性に入れる
.WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); // 改行入れる
var indent = node.GetLeadingTrivia().LastOrDefault(x => x.IsKind(SyntaxKind.WhitespaceTrivia));
return node
.WithoutLeadingTrivia() // 属性のほうに移したのでプロパティ側から全てのLeadingTriviaを除去
.WithLeadingTrivia(indent) // ただしその行のインデントは付与
.AddAttributeLists(attr);
}
else
{
// 既存属性の書き換え
var argList = existingDataMember.ArgumentList;
if (argList == null)
{
// AttributeArgumentList自体がない場合の新規追加
var newArgList = SyntaxFactory.ParseAttributeArgumentList("(Order = \{order++})");
var newDataMember = existingDataMember.WithArgumentList(newArgList);
return node.ReplaceNode(existingDataMember, newDataMember);
}
else
{
// ?.ベンリ
var orderProp = argList.Arguments.FirstOrDefault(x => x?.NameEquals?.Name?.ToFullString()?.Trim() == "Order");
if (orderProp != null)
{
// Orderがある場合は数値を置き換え
var valueToken = (orderProp.Expression as LiteralExpressionSyntax).Token;
return node.ReplaceToken(valueToken, SyntaxFactory.Literal(order++));
}
else
{
// Orderがない場合はOrderの追加
// 完全手作りしたけど、Triviaの調整がダルいのでParseしたほうが100億倍楽
var newOrder = SyntaxFactory.AttributeArgument(
SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName("Order").WithTrailingTrivia(SyntaxFactory.Space)),
null,
SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(order++)).WithLeadingTrivia(SyntaxFactory.Space))
.WithLeadingTrivia(SyntaxFactory.Space);
var newArgList = argList.WithArguments(argList.Arguments.Add(newOrder));
return node.ReplaceNode(argList, newArgList);
}
}
}
});
var newDocument = document.WithSyntaxRoot(newRoot);
return Task.FromResult(newDocument);
}
}
}
@neuecc
Copy link
Author

neuecc commented Dec 7, 2014

↑のコードはTriviaの調整を手作業でやってますが、Formatter.Annotation付与のほうがいいです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment