Skip to content

Commit 320b386

Browse files
committed
Completed SA1141 implementation
1 parent fc44eb9 commit 320b386

20 files changed

Lines changed: 1302 additions & 8 deletions
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.ReadabilityRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CodeActions;
13+
using Microsoft.CodeAnalysis.CodeFixes;
14+
using Microsoft.CodeAnalysis.CSharp;
15+
using Microsoft.CodeAnalysis.CSharp.Syntax;
16+
using Microsoft.CodeAnalysis.Simplification;
17+
using StyleCop.Analyzers.Helpers;
18+
using StyleCop.Analyzers.Lightup;
19+
20+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1141CodeFixProvider))]
21+
[Shared]
22+
internal class SA1141CodeFixProvider : CodeFixProvider
23+
{
24+
/// <inheritdoc/>
25+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
26+
ImmutableArray.Create(SA1141UseTupleSyntax.DiagnosticId);
27+
28+
/// <inheritdoc/>
29+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
30+
{
31+
foreach (var diagnostic in context.Diagnostics)
32+
{
33+
context.RegisterCodeFix(
34+
CodeAction.Create(
35+
ReadabilityResources.SA1141CodeFix,
36+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
37+
nameof(SA1139CodeFixProvider)),
38+
diagnostic);
39+
}
40+
41+
return SpecializedTasks.CompletedTask;
42+
}
43+
44+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
45+
{
46+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
47+
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
48+
49+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
50+
if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression))
51+
{
52+
// get the invocation node when processing ValueTuple.Create, as that needs to be replaced.
53+
node = node.Parent;
54+
}
55+
56+
var newNode = GetReplacementNode(semanticModel, node);
57+
58+
// doing our own formatting, as the default formatting for operators is incompatible with StyleCop.Analyzers.
59+
var separatorRewriter = new SeparatorRewriter();
60+
newNode = separatorRewriter.Visit(newNode);
61+
62+
switch (node.Parent.Kind())
63+
{
64+
case SyntaxKind.MethodDeclaration:
65+
case SyntaxKind.Parameter:
66+
case SyntaxKind.PropertyDeclaration:
67+
case SyntaxKind.IndexerDeclaration:
68+
case SyntaxKind.DelegateDeclaration:
69+
newNode = newNode.WithTrailingTrivia(SyntaxFactory.Space);
70+
break;
71+
}
72+
73+
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, newNode).WithAdditionalAnnotations(Simplifier.Annotation);
74+
return document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
75+
}
76+
77+
private static SyntaxNode GetReplacementNode(SemanticModel semanticModel, SyntaxNode node)
78+
{
79+
switch (node)
80+
{
81+
case QualifiedNameSyntax qualifiedNameSyntax:
82+
return TransformGenericNameToTuple(semanticModel, (GenericNameSyntax)qualifiedNameSyntax.Right);
83+
84+
case GenericNameSyntax genericNameSyntax:
85+
return TransformGenericNameToTuple(semanticModel, genericNameSyntax);
86+
87+
case ObjectCreationExpressionSyntax objectCreationExpression:
88+
return TransformArgumentListToTuple(semanticModel, objectCreationExpression.ArgumentList.Arguments);
89+
90+
case InvocationExpressionSyntax invocationExpressionSyntax:
91+
return TransformArgumentListToTuple(semanticModel, invocationExpressionSyntax.ArgumentList.Arguments);
92+
93+
default:
94+
return node;
95+
}
96+
}
97+
98+
private static SyntaxNode TransformGenericNameToTuple(SemanticModel semanticModel, GenericNameSyntax genericName)
99+
{
100+
var tupleElements = new List<TupleElementSyntaxWrapper>();
101+
102+
foreach (var typeArgument in genericName.TypeArgumentList.Arguments)
103+
{
104+
if (IsValueTuple(semanticModel, typeArgument))
105+
{
106+
var tupleTypeSyntax = (TypeSyntax)GetReplacementNode(semanticModel, typeArgument);
107+
tupleElements.Add(SyntaxFactoryEx.TupleElement(tupleTypeSyntax));
108+
}
109+
else
110+
{
111+
tupleElements.Add(SyntaxFactoryEx.TupleElement(typeArgument));
112+
}
113+
}
114+
115+
var elements = new SeparatedSyntaxListWrapper<TupleElementSyntaxWrapper>.UnknownElementTypeSyntaxList(tupleElements);
116+
return SyntaxFactoryEx.TupleType(elements);
117+
}
118+
119+
private static SyntaxNode TransformArgumentListToTuple(SemanticModel semanticModel, SeparatedSyntaxList<ArgumentSyntax> arguments)
120+
{
121+
SeparatedSyntaxList<ArgumentSyntax> processedArguments = default;
122+
123+
for (var i = 0; i < arguments.Count; i++)
124+
{
125+
var argument = arguments[i];
126+
127+
var argumentTypeInfo = semanticModel.GetTypeInfo(argument.Expression);
128+
if (argumentTypeInfo.Type != argumentTypeInfo.ConvertedType)
129+
{
130+
var expectedType = SyntaxFactory.ParseTypeName(argumentTypeInfo.ConvertedType.ToDisplayString());
131+
argument = argument.WithExpression(SyntaxFactory.CastExpression(expectedType, argument.Expression));
132+
}
133+
134+
processedArguments = processedArguments.Add(argument);
135+
}
136+
137+
return SyntaxFactoryEx.TupleExpression(processedArguments);
138+
}
139+
140+
private static bool IsValueTuple(SemanticModel semanticModel, TypeSyntax typeSyntax)
141+
{
142+
if (typeSyntax.IsKind(SyntaxKindEx.TupleType))
143+
{
144+
return false;
145+
}
146+
147+
var symbolInfo = semanticModel.GetSymbolInfo(typeSyntax);
148+
return (symbolInfo.Symbol is ITypeSymbol typeSymbol) && typeSymbol.IsTupleType();
149+
}
150+
151+
private class SeparatorRewriter : CSharpSyntaxRewriter
152+
{
153+
public SeparatorRewriter()
154+
: base(false)
155+
{
156+
}
157+
158+
public override SyntaxToken VisitToken(SyntaxToken token)
159+
{
160+
if (token.IsKind(SyntaxKind.CommaToken))
161+
{
162+
token = token.WithTrailingTrivia(SyntaxFactory.Space);
163+
}
164+
165+
return base.VisitToken(token);
166+
}
167+
}
168+
}
169+
}

0 commit comments

Comments
 (0)