Created
December 7, 2014 14:36
-
-
Save neuecc/cc1578e8ee85b3ddadcf to your computer and use it in GitHub Desktop.
AddDataMemberWithOrderCodeRefactoringProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
↑のコードはTriviaの調整を手作業でやってますが、Formatter.Annotation付与のほうがいいです。