From e84176c0c4de61430f6b8687ce9d54a6f3985bae Mon Sep 17 00:00:00 2001 From: Richard Stanton Date: Tue, 13 Sep 2016 16:35:12 -0700 Subject: [PATCH] Add MemberThis rule. Rule explicitly adds 'this.' before local member access. --- .../Microsoft.DotNet.CodeFormatting.csproj | 1 + .../Rules/MemberThisRule.cs | 178 ++++++++++++++++++ .../Rules/RuleOrder.cs | 1 + 3 files changed, 180 insertions(+) create mode 100644 src/Microsoft.DotNet.CodeFormatting/Rules/MemberThisRule.cs diff --git a/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj b/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj index 9ef1dc2a..ad76733e 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj +++ b/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj @@ -72,6 +72,7 @@ + diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/MemberThisRule.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/MemberThisRule.cs new file mode 100644 index 00000000..066fd200 --- /dev/null +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/MemberThisRule.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.DotNet.CodeFormatting.Rules +{ + [LocalSemanticRule(MemberThisRule.Name, MemberThisRule.Description, LocalSemanticRuleOrder.MemberThisRule, DefaultRule = false)] + internal sealed class MemberThisRule : CSharpOnlyFormattingRule, ILocalSemanticFormattingRule + { + internal const string Name = "MemberThis"; + internal const string Description = "Add this/Me prefixes on all member references"; + + private sealed class MemberThisRewriter : CSharpSyntaxRewriter + { + private SemanticModel semanticModel; + private readonly CancellationToken cancellationToken; + + internal bool AddedAnnotations + { + get; private set; + } + + internal MemberThisRewriter(SemanticModel semanticModel, CancellationToken cancellationToken) + { + this.semanticModel = semanticModel; + this.cancellationToken = cancellationToken; + } + + public override SyntaxNode VisitGenericName(GenericNameSyntax name) + { + if (this.ShouldModify(name)) + { + this.AddedAnnotations = true; + + // Create a copy of this name + GenericNameSyntax newName = SyntaxFactory.GenericName(name.Identifier, name.TypeArgumentList); + + // Add the "this" to the expression + return SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + newName.WithoutTrivia()) + .WithLeadingTrivia(name.GetLeadingTrivia()) + .WithTrailingTrivia(name.GetTrailingTrivia()); + } + + return base.VisitGenericName(name); + } + + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax name) + { + if (this.ShouldModify(name)) + { + this.AddedAnnotations = true; + + // Create a copy of this name + IdentifierNameSyntax newName = SyntaxFactory.IdentifierName(name.Identifier); + + // Add the "this" to the expression + return SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + newName.WithoutTrivia()) + .WithLeadingTrivia(name.GetLeadingTrivia()) + .WithTrailingTrivia(name.GetTrailingTrivia()); + } + + return base.VisitIdentifierName(name); + } + + private bool ShouldModify(SimpleNameSyntax name) + { + // Ignore cases that don't make sense + if (name.Parent is MemberAccessExpressionSyntax) + { + MemberAccessExpressionSyntax parent = (MemberAccessExpressionSyntax)name.Parent; + if (parent.Name == name) + { + return false; + } + } + else if (name.Parent is QualifiedNameSyntax) + { + QualifiedNameSyntax parent = (QualifiedNameSyntax)name.Parent; + if (parent.Left == name) + { + return false; + } + } + else if (name.Parent is MemberBindingExpressionSyntax || name.Parent is AliasQualifiedNameSyntax) + { + return false; + } + + return this.IsLocalMember(name); + } + + // Check if the name referes to a non-static member on the same type. + private bool IsLocalMember(SimpleNameSyntax name) + { + ISymbol symbol = semanticModel.GetSymbolInfo(name, this.cancellationToken).Symbol; + + // Only add the "this" to non-static member references + if (symbol == null || symbol.IsStatic || !IsMember(symbol)) + { + return false; + } + + // Make sure the reference is in the same type + return IsDerivedType(this.GetParentType(name), symbol.ContainingType); + } + + private static bool IsDerivedType(INamedTypeSymbol type, INamedTypeSymbol baseType) + { + if (type == null) + return false; + if (object.Equals(type, baseType)) + return true; + return IsDerivedType(type.BaseType, baseType); + } + + private static bool IsMember(ISymbol symbol) + { + switch (symbol.Kind) + { + case SymbolKind.Event: + case SymbolKind.Field: + case SymbolKind.Method: + case SymbolKind.Property: + return true; + default: + return false; + } + } + + // Find the type this node resides in + private INamedTypeSymbol GetParentType(SyntaxNode node) + { + while (node != null) + { + if (node.IsKind(SyntaxKind.ClassDeclaration) || node.IsKind(SyntaxKind.StructDeclaration)) + { + return (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(node); + } + + node = node.Parent; + } + return null; + } + } + + /// + /// Entry point to the rule + /// + public async Task ProcessAsync(Document document, SyntaxNode syntaxNode, CancellationToken cancellationToken) + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken); + + // Visit all the nodes in the model + MemberThisRewriter rewriter = new MemberThisRewriter(semanticModel, cancellationToken); + SyntaxNode newNode = rewriter.Visit(syntaxNode); + + // If changes are not mode return the original model + if (!rewriter.AddedAnnotations) + { + return syntaxNode; + } + + return newNode; + } + } +} diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs index 0b1e07cd..45d02a19 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs @@ -29,6 +29,7 @@ internal static class LocalSemanticRuleOrder public const int IsFormattedFormattingRule = 3; public const int RemoveExplicitThisRule = 4; public const int AssertArgumentOrderRule = 5; + public const int MemberThisRule = 6; } // Please keep these values sorted by number, not rule name.