Skip to content

Commit e4a418c

Browse files
authored
Merge pull request #2846 from vweijsters/tuple-analyzers
Implementation of tuple analyzers
2 parents fd10f81 + a85db07 commit e4a418c

44 files changed

Lines changed: 2742 additions & 33 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.NamingRules
5+
{
6+
using System.Collections.Immutable;
7+
using System.Composition;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CodeActions;
12+
using Microsoft.CodeAnalysis.CodeFixes;
13+
using Microsoft.CodeAnalysis.CSharp;
14+
using StyleCop.Analyzers.Helpers;
15+
16+
/// <summary>
17+
/// Implements a code fix for <see cref="SA1316TupleElementNamesShouldUseCorrectCasing"/>.
18+
/// </summary>
19+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1316CodeFixProvider))]
20+
[Shared]
21+
internal class SA1316CodeFixProvider : CodeFixProvider
22+
{
23+
/// <inheritdoc/>
24+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
25+
ImmutableArray.Create(SA1316TupleElementNamesShouldUseCorrectCasing.DiagnosticId);
26+
27+
/// <inheritdoc/>
28+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
29+
{
30+
foreach (var diagnostic in context.Diagnostics)
31+
{
32+
if (diagnostic.Properties.TryGetValue(SA1316TupleElementNamesShouldUseCorrectCasing.ExpectedTupleElementNameKey, out string fixedTupleElementName))
33+
{
34+
context.RegisterCodeFix(
35+
CodeAction.Create(
36+
NamingResources.SA1316CodeFix,
37+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, fixedTupleElementName, cancellationToken),
38+
nameof(SA1316CodeFixProvider)),
39+
diagnostic);
40+
}
41+
}
42+
43+
return SpecializedTasks.CompletedTask;
44+
}
45+
46+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, string fixedTupleElementName, CancellationToken cancellationToken)
47+
{
48+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
49+
50+
var identifierToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
51+
52+
var newSyntaxRoot = syntaxRoot.ReplaceToken(identifierToken, SyntaxFactory.Identifier(fixedTupleElementName).WithTriviaFrom(identifierToken));
53+
return document.WithSyntaxRoot(newSyntaxRoot);
54+
}
55+
}
56+
}
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;
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(SA1141CodeFixProvider)),
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 implementationType = typeof(SeparatedSyntaxListWrapper<>.AutoWrapSeparatedSyntaxList<>).MakeGenericType(typeof(TupleElementSyntaxWrapper), WrapperHelper.GetWrappedType(typeof(TupleElementSyntaxWrapper)));
101+
var tupleElements = (SeparatedSyntaxListWrapper<TupleElementSyntaxWrapper>)Activator.CreateInstance(implementationType);
102+
103+
foreach (var typeArgument in genericName.TypeArgumentList.Arguments)
104+
{
105+
if (IsValueTuple(semanticModel, typeArgument))
106+
{
107+
var tupleTypeSyntax = (TypeSyntax)GetReplacementNode(semanticModel, typeArgument);
108+
tupleElements = tupleElements.Add(SyntaxFactoryEx.TupleElement(tupleTypeSyntax));
109+
}
110+
else
111+
{
112+
tupleElements = tupleElements.Add(SyntaxFactoryEx.TupleElement(typeArgument));
113+
}
114+
}
115+
116+
return SyntaxFactoryEx.TupleType(tupleElements);
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+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CodeActions;
14+
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.CSharp;
16+
using Microsoft.CodeAnalysis.CSharp.Syntax;
17+
using Microsoft.CodeAnalysis.Simplification;
18+
using StyleCop.Analyzers.Helpers;
19+
using StyleCop.Analyzers.Lightup;
20+
21+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1142CodeFixProvider))]
22+
[Shared]
23+
internal class SA1142CodeFixProvider : CodeFixProvider
24+
{
25+
/// <inheritdoc/>
26+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
27+
ImmutableArray.Create(SA1142ReferToTupleElementsByName.DiagnosticId);
28+
29+
public override FixAllProvider GetFixAllProvider() => FixAll.Instance;
30+
31+
/// <inheritdoc/>
32+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
33+
{
34+
foreach (var diagnostic in context.Diagnostics)
35+
{
36+
context.RegisterCodeFix(
37+
CodeAction.Create(
38+
ReadabilityResources.SA1142CodeFix,
39+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
40+
nameof(SA1141CodeFixProvider)),
41+
diagnostic);
42+
}
43+
44+
return SpecializedTasks.CompletedTask;
45+
}
46+
47+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
48+
{
49+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
50+
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
51+
52+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
53+
var replacementNode = GetReplacementNode(semanticModel, node);
54+
55+
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, replacementNode);
56+
return document.WithSyntaxRoot(newSyntaxRoot);
57+
}
58+
59+
private static SyntaxNode GetReplacementNode(SemanticModel semanticModel, SyntaxNode fieldName)
60+
{
61+
var fieldSymbol = (IFieldSymbol)semanticModel.GetSymbolInfo(fieldName.Parent).Symbol;
62+
var fieldNameSymbol = fieldSymbol.ContainingType.GetMembers().OfType<IFieldSymbol>().Single(fs => (fs != fieldSymbol) && (fs.CorrespondingTupleField() == fieldSymbol));
63+
64+
return SyntaxFactory.IdentifierName(fieldNameSymbol.Name).WithTriviaFrom(fieldName);
65+
}
66+
67+
private class FixAll : DocumentBasedFixAllProvider
68+
{
69+
public static FixAllProvider Instance { get; } = new FixAll();
70+
71+
/// <inheritdoc/>
72+
protected override string CodeActionTitle => ReadabilityResources.SA1142CodeFix;
73+
74+
/// <inheritdoc/>
75+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
76+
{
77+
var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
78+
var semanticModel = await document.GetSemanticModelAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
79+
80+
var replaceMap = new Dictionary<SyntaxNode, SyntaxNode>();
81+
82+
foreach (var diagnostic in diagnostics)
83+
{
84+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
85+
replaceMap[node] = GetReplacementNode(semanticModel, node);
86+
}
87+
88+
return syntaxRoot.ReplaceNodes(replaceMap.Keys, (original, rewritten) => replaceMap[original]);
89+
}
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)