1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Collections.Immutable;
|
---|
4 | using System.Composition;
|
---|
5 | using System.Linq;
|
---|
6 | using System.Threading;
|
---|
7 | using System.Threading.Tasks;
|
---|
8 | using Microsoft.CodeAnalysis;
|
---|
9 | using Microsoft.CodeAnalysis.CodeFixes;
|
---|
10 | using Microsoft.CodeAnalysis.CodeActions;
|
---|
11 | using Microsoft.CodeAnalysis.CSharp;
|
---|
12 | using Microsoft.CodeAnalysis.CSharp.Syntax;
|
---|
13 | using Microsoft.CodeAnalysis.Formatting;
|
---|
14 | using Microsoft.CodeAnalysis.Rename;
|
---|
15 | using Microsoft.CodeAnalysis.Text;
|
---|
16 |
|
---|
17 |
|
---|
18 | namespace PersistenceCodeFix {
|
---|
19 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PersistenceCodeFixProvider)), Shared]
|
---|
20 | public class PersistenceCodeFixProvider : CodeFixProvider {
|
---|
21 | private const string title = "Convert to Persistence-4.0";
|
---|
22 |
|
---|
23 | public sealed override ImmutableArray<string> FixableDiagnosticIds {
|
---|
24 | get { return ImmutableArray.Create(MissingStorableTypeAnalyzer.DiagnosticId); }
|
---|
25 | }
|
---|
26 |
|
---|
27 | public sealed override FixAllProvider GetFixAllProvider() {
|
---|
28 | // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
|
---|
29 | return WellKnownFixAllProviders.BatchFixer;
|
---|
30 | }
|
---|
31 |
|
---|
32 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) {
|
---|
33 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
---|
34 |
|
---|
35 | var diagnostic = context.Diagnostics.First();
|
---|
36 | var diagnosticSpan = diagnostic.Location.SourceSpan;
|
---|
37 |
|
---|
38 | // Find the type declaration identified by the diagnostic.
|
---|
39 | var typeDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<BaseTypeDeclarationSyntax>().First();
|
---|
40 |
|
---|
41 | // Register a code action that will invoke the fix.
|
---|
42 | context.RegisterCodeFix(
|
---|
43 | CodeAction.Create(
|
---|
44 | title: title,
|
---|
45 | createChangedSolution: c => AddStorableTypeAttribute(context.Document, typeDeclaration, c),
|
---|
46 | equivalenceKey: title),
|
---|
47 | diagnostic);
|
---|
48 | }
|
---|
49 |
|
---|
50 |
|
---|
51 | private async Task<Solution> AddStorableTypeAttribute(Document document, BaseTypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) {
|
---|
52 | var newGuid = Guid.NewGuid();
|
---|
53 | var attributeLists = typeDecl.AttributeLists.ToDictionary(l => l, l => (AttributeListSyntax)null);
|
---|
54 | bool renamed = false;
|
---|
55 | AttributeListSyntax newAttrList = null;
|
---|
56 | SyntaxList<AttributeListSyntax> oldAttrList;
|
---|
57 |
|
---|
58 | foreach (var kvp in attributeLists.ToArray()) {
|
---|
59 | var newList = SyntaxFactory.AttributeList();
|
---|
60 | foreach (var attr in kvp.Key.Attributes) {
|
---|
61 | if (attr.Name.WithoutTrivia().ToString() == "StorableClass") {
|
---|
62 | // rename
|
---|
63 | var guidArgument = SyntaxFactory.AttributeArgument(
|
---|
64 | SyntaxFactory.LiteralExpression(
|
---|
65 | SyntaxKind.StringLiteralExpression,
|
---|
66 | SyntaxFactory.Literal(newGuid.ToString())));
|
---|
67 |
|
---|
68 | // collect existing arguments (if any) and append the GUID argument
|
---|
69 | var argumentList = new List<AttributeArgumentSyntax>();
|
---|
70 | if (attr.ArgumentList != null) argumentList.AddRange(attr.ArgumentList.Arguments);
|
---|
71 | argumentList.Add(guidArgument);
|
---|
72 |
|
---|
73 | newList = newList.WithAttributes(
|
---|
74 | newList.Attributes.Add(
|
---|
75 | attr.WithName(SyntaxFactory.IdentifierName("StorableType"))
|
---|
76 | .WithLeadingTrivia(attr.GetLeadingTrivia())
|
---|
77 | .WithTrailingTrivia(attr.GetTrailingTrivia())
|
---|
78 | .WithArgumentList(SyntaxFactory.AttributeArgumentList(
|
---|
79 | SyntaxFactory.SeparatedList<AttributeArgumentSyntax>(argumentList)))));
|
---|
80 |
|
---|
81 | renamed = true;
|
---|
82 | } else {
|
---|
83 | newList = newList.WithAttributes(newList.Attributes.Add(attr));
|
---|
84 | }
|
---|
85 | }
|
---|
86 |
|
---|
87 | attributeLists[kvp.Key] = newList
|
---|
88 | .WithLeadingTrivia(kvp.Key.GetLeadingTrivia())
|
---|
89 | .WithTrailingTrivia(kvp.Key.GetTrailingTrivia());
|
---|
90 | }
|
---|
91 |
|
---|
92 | if (!renamed) {
|
---|
93 | oldAttrList = typeDecl.AttributeLists;
|
---|
94 |
|
---|
95 |
|
---|
96 | newAttrList = SyntaxFactory.AttributeList(
|
---|
97 | SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
|
---|
98 | SyntaxFactory.Attribute(
|
---|
99 | SyntaxFactory.IdentifierName("StorableType"))
|
---|
100 | .WithArgumentList(
|
---|
101 | SyntaxFactory.AttributeArgumentList(
|
---|
102 | SyntaxFactory.SingletonSeparatedList<AttributeArgumentSyntax>(
|
---|
103 | SyntaxFactory.AttributeArgument(
|
---|
104 | SyntaxFactory.LiteralExpression(
|
---|
105 | SyntaxKind.StringLiteralExpression,
|
---|
106 | SyntaxFactory.Literal(newGuid.ToString())))
|
---|
107 | )))));
|
---|
108 |
|
---|
109 | } else {
|
---|
110 | oldAttrList = SyntaxFactory.List(attributeLists.Values);
|
---|
111 | }
|
---|
112 |
|
---|
113 | BaseTypeDeclarationSyntax newTypeDecl;
|
---|
114 | var classDecl = typeDecl as ClassDeclarationSyntax;
|
---|
115 | var structDecl = typeDecl as StructDeclarationSyntax;
|
---|
116 | var interfaceDecl = typeDecl as InterfaceDeclarationSyntax;
|
---|
117 | var enumDecl = typeDecl as EnumDeclarationSyntax;
|
---|
118 | if (classDecl != null) {
|
---|
119 | if (newAttrList != null) newTypeDecl = classDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList.Add(newAttrList));
|
---|
120 | else newTypeDecl = classDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList);
|
---|
121 | } else if (structDecl != null) {
|
---|
122 | if (newAttrList != null) newTypeDecl = structDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList.Add(newAttrList));
|
---|
123 | else newTypeDecl = structDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList);
|
---|
124 | } else if (interfaceDecl != null) {
|
---|
125 | if (newAttrList != null) newTypeDecl = interfaceDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList.Add(newAttrList));
|
---|
126 | else newTypeDecl = interfaceDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList);
|
---|
127 | } else if (enumDecl != null) {
|
---|
128 | if (newAttrList != null) newTypeDecl = enumDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList.Add(newAttrList));
|
---|
129 | else newTypeDecl = enumDecl.WithoutLeadingTrivia().WithoutTrailingTrivia().WithAttributeLists(oldAttrList);
|
---|
130 | } else throw new NotSupportedException();
|
---|
131 |
|
---|
132 |
|
---|
133 | var root = await document.GetSyntaxRootAsync(cancellationToken);
|
---|
134 |
|
---|
135 | var oldUsings = (root as CompilationUnitSyntax).Usings;
|
---|
136 |
|
---|
137 |
|
---|
138 | var newRoot = root
|
---|
139 | .ReplaceNode(typeDecl, newTypeDecl
|
---|
140 | .WithAdditionalAnnotations(Formatter.Annotation)
|
---|
141 | .WithLeadingTrivia(typeDecl.GetLeadingTrivia())
|
---|
142 | .WithTrailingTrivia(typeDecl.GetTrailingTrivia())
|
---|
143 | ) as CompilationUnitSyntax;
|
---|
144 |
|
---|
145 |
|
---|
146 | // add using HeuristicLab.Persistence if something was changed or added and the document does already contain the using directive
|
---|
147 | if ((newAttrList != null || renamed) &&
|
---|
148 | oldUsings.All(sn => sn.Name.WithoutTrivia().ToString() != "HeuristicLab.Persistence")) {
|
---|
149 | var newUsing = SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("HeuristicLab.Persistence"));
|
---|
150 | newRoot = newRoot.WithUsings(oldUsings.Add(newUsing));
|
---|
151 | }
|
---|
152 |
|
---|
153 | return document
|
---|
154 | .WithSyntaxRoot(newRoot)
|
---|
155 | .Project.Solution;
|
---|
156 | }
|
---|
157 | }
|
---|
158 | } |
---|