Skip to content
This repository was archived by the owner on Jul 12, 2022. It is now read-only.

Commit a920f5b

Browse files
Add MemberThis rule. Rule explicitly adds 'this.' before local member access.
1 parent 313ab9f commit a920f5b

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<Compile Include="NameHelper.cs" />
7373
<Compile Include="ResponseFileWorkspace.cs" />
7474
<Compile Include="Rules\MarkReadonlyFieldsRule.cs" />
75+
<Compile Include="Rules\MemberThisRule.cs" />
7576
<Compile Include="SemaphoreLock.cs" />
7677
<Compile Include="SyntaxUtil.cs" />
7778
<Compile Include="Filters\FilenameFilter.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
11+
namespace Microsoft.DotNet.CodeFormatting.Rules
12+
{
13+
[LocalSemanticRule(MemberThisRule.Name, MemberThisRule.Description, LocalSemanticRuleOrder.MemberThisRule, DefaultRule = false)]
14+
internal sealed class MemberThisRule : CSharpOnlyFormattingRule, ILocalSemanticFormattingRule
15+
{
16+
internal const string Name = "MemberThis";
17+
internal const string Description = "Add this/Me prefixes on all member references";
18+
19+
private sealed class MemberThisRewriter : CSharpSyntaxRewriter
20+
{
21+
private SemanticModel semanticModel;
22+
private readonly CancellationToken cancellationToken;
23+
24+
internal bool AddedAnnotations
25+
{
26+
get; private set;
27+
}
28+
29+
internal MemberThisRewriter(SemanticModel semanticModel, CancellationToken cancellationToken)
30+
{
31+
this.semanticModel = semanticModel;
32+
this.cancellationToken = cancellationToken;
33+
}
34+
35+
public override SyntaxNode VisitGenericName(GenericNameSyntax name)
36+
{
37+
if (this.ShouldModify(name))
38+
{
39+
this.AddedAnnotations = true;
40+
41+
// Create a copy of this name
42+
GenericNameSyntax newName = SyntaxFactory.GenericName(name.Identifier, name.TypeArgumentList);
43+
44+
// Add the "this" to the expression
45+
return SyntaxFactory.MemberAccessExpression(
46+
SyntaxKind.SimpleMemberAccessExpression,
47+
SyntaxFactory.ThisExpression(),
48+
newName.WithoutTrivia())
49+
.WithLeadingTrivia(name.GetLeadingTrivia())
50+
.WithTrailingTrivia(name.GetTrailingTrivia());
51+
}
52+
53+
return base.VisitGenericName(name);
54+
}
55+
56+
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax name)
57+
{
58+
if (this.ShouldModify(name))
59+
{
60+
this.AddedAnnotations = true;
61+
62+
// Create a copy of this name
63+
IdentifierNameSyntax newName = SyntaxFactory.IdentifierName(name.Identifier);
64+
65+
// Add the "this" to the expression
66+
return SyntaxFactory.MemberAccessExpression(
67+
SyntaxKind.SimpleMemberAccessExpression,
68+
SyntaxFactory.ThisExpression(),
69+
newName.WithoutTrivia())
70+
.WithLeadingTrivia(name.GetLeadingTrivia())
71+
.WithTrailingTrivia(name.GetTrailingTrivia());
72+
}
73+
74+
return base.VisitIdentifierName(name);
75+
}
76+
77+
private bool ShouldModify(SimpleNameSyntax name)
78+
{
79+
// Ignore cases that don't make sense
80+
if (name.Parent is MemberAccessExpressionSyntax)
81+
{
82+
MemberAccessExpressionSyntax parent = (MemberAccessExpressionSyntax)name.Parent;
83+
if (parent.Name == name)
84+
{
85+
return false;
86+
}
87+
}
88+
else if (name.Parent is QualifiedNameSyntax)
89+
{
90+
QualifiedNameSyntax parent = (QualifiedNameSyntax)name.Parent;
91+
if (parent.Left == name)
92+
{
93+
return false;
94+
}
95+
}
96+
else if (name.Parent is MemberBindingExpressionSyntax || name.Parent is AliasQualifiedNameSyntax)
97+
{
98+
return false;
99+
}
100+
101+
return this.IsLocalMember(name);
102+
}
103+
104+
// Check if the name referes to a non-static member on the same type.
105+
private bool IsLocalMember(SimpleNameSyntax name)
106+
{
107+
ISymbol symbol = semanticModel.GetSymbolInfo(name, this.cancellationToken).Symbol;
108+
109+
// Only add the "this" to non-static member references
110+
if (symbol == null || symbol.IsStatic || !IsMember(symbol))
111+
{
112+
return false;
113+
}
114+
115+
// Make sure the reference is in the same type
116+
return IsDerivedType(this.GetParentType(name), symbol.ContainingType);
117+
}
118+
119+
private static bool IsDerivedType(INamedTypeSymbol type, INamedTypeSymbol baseType)
120+
{
121+
if (type == null)
122+
return false;
123+
if (object.Equals(type, baseType))
124+
return true;
125+
return IsDerivedType(type.BaseType, baseType);
126+
}
127+
128+
private static bool IsMember(ISymbol symbol)
129+
{
130+
switch (symbol.Kind)
131+
{
132+
case SymbolKind.Event:
133+
case SymbolKind.Field:
134+
case SymbolKind.Method:
135+
case SymbolKind.Property:
136+
return true;
137+
default:
138+
return false;
139+
}
140+
}
141+
142+
// Find the type this node resides in
143+
private INamedTypeSymbol GetParentType(SyntaxNode node)
144+
{
145+
while (node != null)
146+
{
147+
if (node.IsKind(SyntaxKind.ClassDeclaration) || node.IsKind(SyntaxKind.StructDeclaration))
148+
{
149+
return (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(node);
150+
}
151+
152+
node = node.Parent;
153+
}
154+
return null;
155+
}
156+
}
157+
158+
/// <summary>
159+
/// Entry point to the rule
160+
/// </summary>
161+
public async Task<SyntaxNode> ProcessAsync(Document document, SyntaxNode syntaxNode, CancellationToken cancellationToken)
162+
{
163+
SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken);
164+
165+
// Visit all the nodes in the model
166+
MemberThisRewriter rewriter = new MemberThisRewriter(semanticModel, cancellationToken);
167+
SyntaxNode newNode = rewriter.Visit(syntaxNode);
168+
169+
// If changes are not mode return the original model
170+
if (!rewriter.AddedAnnotations)
171+
{
172+
return syntaxNode;
173+
}
174+
175+
return newNode;
176+
}
177+
}
178+
}

src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal static class LocalSemanticRuleOrder
2929
public const int IsFormattedFormattingRule = 3;
3030
public const int RemoveExplicitThisRule = 4;
3131
public const int AssertArgumentOrderRule = 5;
32+
public const int MemberThisRule = 6;
3233
}
3334

3435
// Please keep these values sorted by number, not rule name.

0 commit comments

Comments
 (0)