Skip to content

Commit

Permalink
feat: Adds UseNameofExpressionInDependencyPropertyDeclarations
Browse files Browse the repository at this point in the history
  • Loading branch information
ironcev committed May 25, 2018
1 parent 160f96f commit 8af4754
Show file tree
Hide file tree
Showing 8 changed files with 928 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Sharpen.Engine/Sharpen.Engine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
<Compile Include="Extensions\SyntaxNodeExtensions.cs" />
<Compile Include="Extensions\SyntaxTokenExtensions.cs" />
<Compile Include="Extensions\TextSpanExtensions.cs" />
<Compile Include="SharpenSuggestions\CSharp60\NameofExpressions\UseNameofExpressionInDependencyPropertyDeclarations.cs" />
<Compile Include="SharpenSuggestions\CSharp60\NameofExpressions\UseNameofExpressionForThrowingArgumentExceptions.cs" />
<Compile Include="SharpenSuggestions\CSharp70\ExpressionBodiedMembers\UseExpressionBodyForLocalFunctions.cs" />
<Compile Include="SharpenSuggestions\CSharp70\OutVariables\BaseUseOutVariables.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/Sharpen.Engine/SharpenEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class SharpenEngine
UseExpressionBodyForGetOnlyIndexers.Instance,

UseNameofExpressionForThrowingArgumentExceptions.Instance,
UseNameofExpressionInDependencyPropertyDeclarations.Instance,

// C# 7.0.
UseExpressionBodyForConstructors.Instance,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Sharpen.Engine.Extensions;

namespace Sharpen.Engine.SharpenSuggestions.CSharp60.NameofExpressions
{
internal sealed class UseNameofExpressionInDependencyPropertyDeclarations : ISharpenSuggestion, ISingleSyntaxTreeAnalyzer
{
private static readonly SyntaxKind[] RequiredDependencyPropertyFieldModifiers = { SyntaxKind.ReadOnlyKeyword, SyntaxKind.StaticKeyword };
private static readonly ArgumentSyntax[] EmptyArgumentSyntaxArray = new ArgumentSyntax[0];

private UseNameofExpressionInDependencyPropertyDeclarations() { }

public string MinimumLanguageVersion { get; } = CSharpLanguageVersions.CSharp60;

public ICSharpFeature LanguageFeature { get; } = CSharpFeatures.NameofExpressions.Instance;

public string FriendlyName { get; } = "Use nameof expression in dependency property declarations";

public static readonly UseNameofExpressionInDependencyPropertyDeclarations Instance = new UseNameofExpressionInDependencyPropertyDeclarations();

private static string GetRegisterMethodDisplayText(SyntaxNode syntaxNode) // SyntaxNode is actually ArgumentSyntax.
{
return syntaxNode.FirstAncestorOrSelf<InvocationExpressionSyntax>().ToString();
}

public IEnumerable<AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
{
return syntaxTree.GetRoot()
.DescendantNodes()
.OfType<FieldDeclarationSyntax>()
.Select(GetPropertyNameArgumentsIfFieldDeclarationIsDependencyPropertyDeclaration)
.Where(propertyNames => propertyNames.Length > 0)
.SelectMany(propertyNames => propertyNames)
.Select(propertyName => new AnalysisResult
(
this,
analysisContext,
syntaxTree.FilePath,
propertyName.GetFirstToken(),
propertyName,
GetRegisterMethodDisplayText
));

ArgumentSyntax[] GetPropertyNameArgumentsIfFieldDeclarationIsDependencyPropertyDeclaration(FieldDeclarationSyntax fieldDeclaration)
{
// Let's first do fast checks that quickly and cheaply eliminate obvious non-candidates.
// We want to use the semantic model only if we are sure that we have candidates that are
// very likely field declarations that we are looking for.

if (!(RequiredDependencyPropertyFieldModifiers.All(modifier =>
fieldDeclaration.Modifiers.Select(token => token.Kind()).Contains(modifier)) &&
fieldDeclaration.Declaration.Type.GetLastToken().ValueText == "DependencyProperty"))
return EmptyArgumentSyntaxArray;

return fieldDeclaration
.Declaration
.Variables
.Where(variableDeclaration =>
variableDeclaration.Identifier.ValueText.EndsWith("Property", StringComparison.Ordinal) &&
variableDeclaration.Initializer != null &&
variableDeclaration.Initializer.Value.IsKind(SyntaxKind.InvocationExpression))
.Select(variableDeclaration => new
{
FieldName = variableDeclaration.Identifier.ValueText,
Invocation = (InvocationExpressionSyntax)variableDeclaration.Initializer.Value
})
.Where(fieldNameAndInvocation =>
fieldNameAndInvocation.Invocation.Expression.GetLastToken().ValueText == "Register" &&
fieldNameAndInvocation.Invocation.ArgumentList.Arguments.Count > 0 &&
fieldNameAndInvocation.Invocation.ArgumentList.Arguments[0].Expression.IsKind(SyntaxKind.StringLiteralExpression) &&
fieldNameAndInvocation.Invocation.ArgumentList.Arguments[0].Expression.GetLastToken().ValueText is string propertyName &&
fieldNameAndInvocation.FieldName.StartsWith(propertyName, StringComparison.Ordinal) &&
fieldNameAndInvocation.FieldName.Length == propertyName.Length + "Property".Length && // Check that they are equal without creating temporary strings.
InstancePropertyWithPropertyNameExists(propertyName, fieldNameAndInvocation.Invocation)
&&
// If we reach this point it means that we have a field declaration that in a real case most likely
// 99.9999 % percent sure represents a dependency property declaration. Cool :-)
// Still, we have to check for those 0.00001% since we could have a situation sketched
// in the smoke tests, where the Register method or the DependencyProperty class are not the "real" one.
// I am asking myself now if this level of paranoia is really appropriate considering the cost of
// this additional analysis. Hmmm.
// TODO-THINK: We can provide an analysis option like "Use optimistic analysis" that would skip the below test.
semanticModel.GetSymbolInfo(fieldNameAndInvocation.Invocation).Symbol is IMethodSymbol method &&
method.ContainingType?.Name == "DependencyProperty" &&
(method.ContainingType.ContainingType == null || method.ContainingType.ContainingType.IsNamespace) && // It must not be nested in another type.
method.ContainingType.ContainingNamespace.FullNameIsEqualTo("System.Windows")
// To be really sure, we should check here that the DependencyProperty type is
// really the System.Windows.DependencyProperty. And this check would add a whole
// bunch of super crazy paranoia on already paranoid check ;-)
// Basically, the only possibility for this check to fail would be if someone
// introduces it's own type named DependencyProperty that implicitly converts
// the System.Windows.DependencyProperty to itself. This is demonstrated in
// smoke tests but it is completely crazy. Who would ever do that?
// And since this check would be quite complex to implement, we will simply skip it.
)
.Select(fieldNameAndInvocation => fieldNameAndInvocation.Invocation.ArgumentList.Arguments[0])
.ToArray();

bool InstancePropertyWithPropertyNameExists(string propertyName, SyntaxNode anyChildNodeWithinTypeDeclaration)
{
var typeDeclaration = anyChildNodeWithinTypeDeclaration.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null) return false;

// We could cache the information about non static properties per type declaration
// to avoid traversing the tree every time.
// But in the real world the number of dependency properties per type is very moderate.
// This would most likely be a premature optimization.
// So let's leave it this way.
return typeDeclaration
.DescendantNodes()
.OfType<PropertyDeclarationSyntax>(
)
.Any(property =>
property.Modifiers.All(token => !token.IsKind(SyntaxKind.StaticKeyword)) &&
property.Identifier.ValueText == propertyName
);
}
}
}
}
}
7 changes: 7 additions & 0 deletions tests/smoke/CSharp60/CSharp60.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
Expand All @@ -40,6 +42,7 @@
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExpressionBodiedMembers\UseExpressionBodyForGetOnlyIndexers\GetOnlyIndexersThatAlreadyHaveExpressionBody.cs" />
Expand All @@ -59,6 +62,10 @@
<Compile Include="NameofExpressions\UseNameofExpressionForThrowingArgumentExceptions\ArgumentExceptionsThatAreNotCandidatesToUseNameofExpression.cs" />
<Compile Include="NameofExpressions\UseNameofExpressionForThrowingArgumentExceptions\ArgumentExceptionsInEventsThatAreCandidatesToUseNameofExpression.cs" />
<Compile Include="NameofExpressions\UseNameofExpressionForThrowingArgumentExceptions\SomeEnum.cs" />
<Compile Include="NameofExpressions\UseNameofExpressionInDependencyPropertyDeclarations\DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression.cs" />
<Compile Include="NameofExpressions\UseNameofExpressionInDependencyPropertyDeclarations\DependencyPropertyDeclarationsThatAreNotCandidatesToUseNameofExpression.cs" />
<Compile Include="NameofExpressions\UseNameofExpressionInDependencyPropertyDeclarations\DependencyPropertyDeclarationsInSingleFieldDeclarationThatAreCandidatesToUseNameofExpression.cs" />
<Compile Include="NameofExpressions\UseNameofExpressionInDependencyPropertyDeclarations\DependencyPropertyDeclarationsInFieldsDeclarationsThatAreCandidatesToUseNameofExpression.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// ReSharper disable All

// Expected number of suggestions: 15

using System.Windows;
using static System.Windows.DependencyProperty;

namespace CSharp60.NameofExpressions.UseNameofExpressionInDependencyPropertyDeclarations
{
class DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression
{
public static readonly DependencyProperty StringProperty;

public static readonly DependencyProperty DoubleProperty;

public static readonly DependencyProperty FloatProperty;

public static readonly DependencyProperty IntProperty;

public static readonly DependencyProperty BooleanProperty;

public static readonly DependencyProperty OtherStringProperty;

public static readonly DependencyProperty OtherDoubleProperty;

public static readonly DependencyProperty OtherFloatProperty;

public static readonly DependencyProperty OtherIntProperty;

public static readonly DependencyProperty OtherBooleanProperty;

public static readonly System.Windows.DependencyProperty MoreStringProperty;

public static readonly System.Windows.DependencyProperty MoreDoubleProperty;

public static readonly System.Windows.DependencyProperty MoreFloatProperty;

public static readonly System.Windows.DependencyProperty MoreIntProperty;

public static readonly System.Windows.DependencyProperty MoreBooleanProperty;

static DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression()
{
StringProperty =
DependencyProperty.Register("String", typeof(string), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression));

DoubleProperty =
DependencyProperty.Register("Double", typeof(double), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata());

FloatProperty =
DependencyProperty.Register("Float", typeof(float), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(float)));

IntProperty =
DependencyProperty.Register("Int", typeof(int), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(int), OnDependencyPropertyChanged));

BooleanProperty =
DependencyProperty.Register("Boolean", typeof(bool), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(bool), OnDependencyPropertyChanged, CoerceValueCallback));

OtherStringProperty =
Register("OtherString", typeof(string), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression));

OtherDoubleProperty =
Register("OtherDouble", typeof(double), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata());

OtherFloatProperty =
Register("OtherFloat", typeof(float), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(float)));

OtherIntProperty =
Register("OtherInt", typeof(int), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(int), OnDependencyPropertyChanged));

OtherBooleanProperty =
Register("OtherBoolean", typeof(bool), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(bool), OnDependencyPropertyChanged, CoerceValueCallback));

MoreStringProperty =
DependencyProperty.Register("MoreString", typeof(string), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression));

MoreDoubleProperty =
DependencyProperty.Register("MoreDouble", typeof(double), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata());

MoreFloatProperty =
DependencyProperty.Register("MoreFloat", typeof(float), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(float)));

MoreIntProperty =
DependencyProperty.Register("MoreInt", typeof(int), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(int), OnDependencyPropertyChanged));

MoreBooleanProperty =
DependencyProperty.Register("MoreBoolean", typeof(bool), typeof(DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression),
new PropertyMetadata(default(bool), OnDependencyPropertyChanged, CoerceValueCallback));
}


public string String { get; set; }
public double Double { get; set; }
public float Float { get; set; }
public int Int { get; set; }
public float Boolean { get; set; }
public string OtherString { get; set; }
public double OtherDouble { get; set; }
public float OtherFloat { get; set; }
public int OtherInt { get; set; }
public float OtherBoolean { get; set; }
public string MoreString { get; set; }
public double MoreDouble { get; set; }
public float MoreFloat { get; set; }
public int MoreInt { get; set; }
public float MoreBoolean { get; set; }

public static void OnDependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
}

private static object CoerceValueCallback(DependencyObject dependencyObject, object baseValue)
{
return null;
}
}
}
Loading

0 comments on commit 8af4754

Please sign in to comment.