Skip to content

Commit 421a086

Browse files
committed
SA1142 implementation
1 parent 2efacec commit 421a086

8 files changed

Lines changed: 370 additions & 0 deletions

File tree

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(SA1142ReferToTupleFieldsByName.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+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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.Test.CSharp7.ReadabilityRules
5+
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
9+
using StyleCop.Analyzers.ReadabilityRules;
10+
using Xunit;
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
12+
StyleCop.Analyzers.ReadabilityRules.SA1142ReferToTupleFieldsByName,
13+
StyleCop.Analyzers.ReadabilityRules.SA1142CodeFixProvider>;
14+
15+
/// <summary>
16+
/// This class contains the CSharp 7.x unit tests for SA1142.
17+
/// </summary>
18+
/// <seealso cref="SA1142ReferToTupleFieldsByName"/>
19+
/// <seealso cref="SA1142CodeFixProvider"/>
20+
public class SA1142CSharp7UnitTests
21+
{
22+
/// <summary>
23+
/// Validate that tuple fields that are referenced by their name will not produce any diagnostics.
24+
/// </summary>
25+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
26+
[Fact]
27+
public async Task ValidateFieldNameReferencesAsync()
28+
{
29+
var testCode = @"
30+
public class TestClass
31+
{
32+
public int TestMethod((int nameA, int nameB) p1, (int, int) p2, (int, int nameC) p3)
33+
{
34+
return p1.nameA + p1.nameB + p2.Item1 + p2.Item2 + p3.Item1 + p3.nameC;
35+
}
36+
}
37+
";
38+
39+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
40+
}
41+
42+
/// <summary>
43+
/// Verify that tuple names referenced by their metadata name will produce the expected diagnostics.
44+
/// </summary>
45+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
46+
[Fact]
47+
public async Task ValidateMetadataNameReferencesAsync()
48+
{
49+
var testCode = @"
50+
public class TestClass
51+
{
52+
public int TestMethod1((int nameA, int nameB) p1)
53+
{
54+
return p1.[|Item1|] + p1.[|Item2|] /* test */ + p1.ToString().Length;
55+
}
56+
57+
public int TestMethod2((int nameA, (int subNameA, int subNameB) nameB) p1)
58+
{
59+
return p1.[|Item1|] + p1.nameB.[|Item1|] + p1.[|Item2|].[|Item2|];
60+
}
61+
}
62+
";
63+
64+
var fixedCode = @"
65+
public class TestClass
66+
{
67+
public int TestMethod1((int nameA, int nameB) p1)
68+
{
69+
return p1.nameA + p1.nameB /* test */ + p1.ToString().Length;
70+
}
71+
72+
public int TestMethod2((int nameA, (int subNameA, int subNameB) nameB) p1)
73+
{
74+
return p1.nameA + p1.nameB.subNameA + p1.nameB.subNameB;
75+
}
76+
}
77+
";
78+
79+
DiagnosticResult[] expectedDiagnostics =
80+
{
81+
// diagnostics are specified inline
82+
};
83+
84+
await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedCode, CancellationToken.None).ConfigureAwait(false);
85+
}
86+
}
87+
}

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,4 +561,16 @@
561561
<data name="SA1141CodeFix" xml:space="preserve">
562562
<value>Replace with tuple syntax</value>
563563
</data>
564+
<data name="SA1142CodeFix" xml:space="preserve">
565+
<value>Use tuple field name</value>
566+
</data>
567+
<data name="SA1142Description" xml:space="preserve">
568+
<value>A field of a tuple was referenced by its metadata name when a field name is available.</value>
569+
</data>
570+
<data name="SA1142MessageFormat" xml:space="preserve">
571+
<value>Refer to tuple fields by name</value>
572+
</data>
573+
<data name="SA1142Title" xml:space="preserve">
574+
<value>Refer to tuple fields by name</value>
575+
</data>
564576
</root>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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.Linq;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using StyleCop.Analyzers.Helpers;
14+
using StyleCop.Analyzers.Lightup;
15+
16+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
17+
internal class SA1142ReferToTupleFieldsByName : DiagnosticAnalyzer
18+
{
19+
/// <summary>
20+
/// The ID for diagnostics produced by the <see cref="SA1142ReferToTupleFieldsByName"/> analyzer.
21+
/// </summary>
22+
public const string DiagnosticId = "SA1142";
23+
24+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(ReadabilityResources.SA1142Title), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
25+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1142MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
26+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1142Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
27+
private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1142.md";
28+
29+
private static readonly Action<SyntaxNodeAnalysisContext> SimpleMemberAccessExpressionAction = HandleSimpleMemberAccessExpression;
30+
31+
private static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
32+
33+
/// <inheritdoc/>
34+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
35+
36+
/// <inheritdoc/>
37+
public override void Initialize(AnalysisContext context)
38+
{
39+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
40+
context.EnableConcurrentExecution();
41+
42+
context.RegisterSyntaxNodeAction(SimpleMemberAccessExpressionAction, SyntaxKind.SimpleMemberAccessExpression);
43+
}
44+
45+
private static void HandleSimpleMemberAccessExpression(SyntaxNodeAnalysisContext context)
46+
{
47+
if (!context.SupportsTuples())
48+
{
49+
return;
50+
}
51+
52+
var memberAccessExpression = (MemberAccessExpressionSyntax)context.Node;
53+
54+
var fieldSymbol = context.SemanticModel.GetSymbolInfo(memberAccessExpression).Symbol as IFieldSymbol;
55+
if (fieldSymbol == null)
56+
{
57+
return;
58+
}
59+
60+
if (CheckFieldName(fieldSymbol))
61+
{
62+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, memberAccessExpression.Name.GetLocation()));
63+
}
64+
}
65+
66+
private static bool CheckFieldName(IFieldSymbol fieldSymbol)
67+
{
68+
if (!fieldSymbol.ContainingType.IsTupleType())
69+
{
70+
return false;
71+
}
72+
73+
// check if this already is a proper tuple field name
74+
if (fieldSymbol.CorrespondingTupleField() != fieldSymbol)
75+
{
76+
return false;
77+
}
78+
79+
// check if there is a tuple field name declared.
80+
return fieldSymbol.ContainingType.GetMembers().OfType<IFieldSymbol>().Count(fs => fs.CorrespondingTupleField() == fieldSymbol) > 1;
81+
}
82+
}
83+
}

StyleCopAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat
116116
documentation\SA1137.md = documentation\SA1137.md
117117
documentation\SA1139.md = documentation\SA1139.md
118118
documentation\SA1141.md = documentation\SA1141.md
119+
documentation\SA1142.md = documentation\SA1142.md
119120
documentation\SA1200.md = documentation\SA1200.md
120121
documentation\SA1201.md = documentation\SA1201.md
121122
documentation\SA1202.md = documentation\SA1202.md

documentation/ReadabilityRules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ Identifier | Name | Description
4242
[SA1137](SA1137.md) | ElementsShouldHaveTheSameIndentation | Two sibling elements which each start on their own line have different levels of indentation.
4343
[SA1139](SA1139.md) | UseLiteralsSuffixNotationInsteadOfCasting | Use literal suffix notation instead of casting.
4444
[SA1141](SA1141.md) | UseTupleSyntax | Use tuple syntax instead of the underlying ValueTuple implementation type.
45+
[SA1142](SA1142.md) | ReferToTupleFieldsByName | A field of a tuple was referenced by its metadata name when a field name is available.

documentation/SA1142.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## SA1142
2+
3+
<table>
4+
<tr>
5+
<td>TypeName</td>
6+
<td>SA1142ReferToTupleFieldsByName</td>
7+
</tr>
8+
<tr>
9+
<td>CheckId</td>
10+
<td>SA1142</td>
11+
</tr>
12+
<tr>
13+
<td>Category</td>
14+
<td>Readability Rules</td>
15+
</tr>
16+
</table>
17+
18+
:memo: This rule is new for StyleCop Analyzers, and was not present in StyleCop Classic.
19+
:memo: This rule is only active for C# 7.0 and higher
20+
21+
## Cause
22+
23+
A field of a tuple was referenced by its metadata name when a field name is available.
24+
25+
## Rule description
26+
27+
A field of a tuple was referenced by its metadata name when a field name is available. See the documentation on [tuple types](https://docs.microsoft.com/en-us/dotnet/csharp/tuples) for information on how to work with tuples in C# 7.
28+
29+
For example, the following code would produce a violation of this rule:
30+
31+
```csharp
32+
(int valueA, int valueB) x;
33+
34+
var y = x.Item1;
35+
```
36+
37+
The following code would not produce any violations:
38+
39+
```csharp
40+
(int valueA, int valueB) x;
41+
42+
var y = x.valueA;
43+
```
44+
45+
## How to fix violations
46+
47+
To fix a violation of this rule, use the appropriate tuple field name in code instead of the metadata name.
48+
49+
## How to suppress violations
50+
51+
```csharp
52+
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1142:ReferToTupleFieldsByName", Justification = "Reviewed.")]
53+
```
54+
55+
```csharp
56+
#pragma warning disable SA1142 // Refer to tuple fields by name
57+
#pragma warning restore SA1142 // Refer to tuple fields by name
58+
```

0 commit comments

Comments
 (0)