-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Adds UseNameofExpressionInDependencyPropertyDeclarations
- Loading branch information
Showing
8 changed files
with
928 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
...estions/CSharp60/NameofExpressions/UseNameofExpressionInDependencyPropertyDeclarations.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
...ions/DependencyPropertyDeclarationsInConstructorThatAreCandidatesToUseNameofExpression.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.