/// <inheritdoc/> public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var semanticModel = await context.Document .GetSemanticModelAsync(context.CancellationToken) .ConfigureAwait(false); var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); foreach (var diagnostic in context.Diagnostics) { var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); if (string.IsNullOrEmpty(token.ValueText)) { continue; } var argument = syntaxRoot.FindNode(diagnostic.Location.SourceSpan) .FirstAncestorOrSelf <ArgumentSyntax>(); var type = semanticModel.GetDeclaredSymbolSafe(argument?.FirstAncestorOrSelf <ClassDeclarationSyntax>(), context.CancellationToken); if (PropertyChanged.TryGetOnPropertyChanged(type, semanticModel, context.CancellationToken, out var invoker)) { context.RegisterDocumentEditorFix( "Use overload that does not use expression.", (editor, cancellationToken) => RemoveExpression(editor, argument, invoker, underscoreFields, cancellationToken), this.GetType(), diagnostic); } } }
private static void MakeAutoPropertyNotifyWhenValueChanges(DocumentEditor editor, PropertyDeclarationSyntax propertyDeclaration, IMethodSymbol invoker, SemanticModel semanticModel, CancellationToken cancellationToken) { if (Property.IsMutableAutoProperty(propertyDeclaration, out var getter, out var setter)) { if (getter.Body != null || getter.ContainsSkippedText || setter.Body != null || setter.ContainsSkippedText) { return; } var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); var property = semanticModel.GetDeclaredSymbolSafe(propertyDeclaration, cancellationToken); var backingField = editor.AddBackingField(propertyDeclaration, underscoreFields, cancellationToken); var fieldAccess = underscoreFields ? backingField.Name() : $"this.{backingField.Name()}"; var code = StringBuilderPool.Borrow() .AppendLine($"public Type PropertyName") .AppendLine("{") .AppendLine($" get => {fieldAccess};") .AppendLine() .AppendLine(" set") .AppendLine(" {") .AppendLine($" if ({Snippet.EqualityCheck(property.Type, "value", fieldAccess, semanticModel)})") .AppendLine(" {") .AppendLine($" return;") .AppendLine(" }") .AppendLine() .AppendLine($" {fieldAccess} = value;") .AppendLine($" {Snippet.OnPropertyChanged(invoker, property.Name, underscoreFields)}") .AppendLine(" }") .AppendLine("}") .Return(); var template = ParseProperty(code); editor.ReplaceNode( getter, x => x.WithExpressionBody(template.Getter().ExpressionBody) .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation)); editor.ReplaceNode( setter, x => x.WithBody(template.Setter().Body) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) .WithAdditionalAnnotations(Formatter.Annotation)); if (propertyDeclaration.Initializer != null) { editor.ReplaceNode( propertyDeclaration, x => x.WithoutInitializer()); } editor.ReplaceNode(propertyDeclaration, x => x.WithAdditionalAnnotations(Formatter.Annotation)); } }
private static void MakeAutoPropertySet(DocumentEditor editor, PropertyDeclarationSyntax propertyDeclaration, IMethodSymbol setAndRaise, SemanticModel semanticModel, CancellationToken cancellationToken) { var classDeclaration = propertyDeclaration.FirstAncestorOrSelf <ClassDeclarationSyntax>(); if (classDeclaration == null) { return; } if (Property.IsMutableAutoProperty(propertyDeclaration, out var getter, out var setter)) { if (getter.Body != null || getter.ContainsSkippedText || setter.Body != null || setter.ContainsSkippedText) { return; } var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); var backingField = editor.AddBackingField(propertyDeclaration, underscoreFields, cancellationToken); var fieldAccess = underscoreFields ? backingField.Name() : $"this.{backingField.Name()}"; var code = StringBuilderPool.Borrow() .AppendLine($"public Type PropertyName") .AppendLine("{") .AppendLine($" get => {fieldAccess};") .AppendLine($" set => {(underscoreFields ? string.Empty : "this.")}{setAndRaise.Name}(ref {fieldAccess}, value);") .AppendLine("}") .Return(); var template = ParseProperty(code); editor.ReplaceNode( getter, x => x.WithExpressionBody(template.Getter().ExpressionBody) .WithLeadingLineFeed()); editor.ReplaceNode( setter, x => x.WithExpressionBody(template.Setter().ExpressionBody) .WithLeadingLineFeed() .WithTrailingLineFeed()); if (propertyDeclaration.Initializer != null) { editor.ReplaceNode( propertyDeclaration, (node, g) => ((PropertyDeclarationSyntax)node).WithoutInitializer()); } editor.ReplaceNode(propertyDeclaration, x => x.WithAdditionalAnnotations(Formatter.Annotation)); } }
private static void ApplyFix(DocumentEditor editor, ArgumentSyntax argument, string name, CancellationToken cancellationToken) { if (!IsStaticContext(argument, editor.SemanticModel, cancellationToken) && editor.SemanticModel.LookupSymbols(argument.SpanStart, name: name).TrySingle(out var member) && (member is IFieldSymbol || member is IPropertySymbol || member is IMethodSymbol) && !member.IsStatic && !CodeStyle.UnderscoreFields(editor.SemanticModel)) { editor.ReplaceNode( argument.Expression, (x, _) => SyntaxFactory.ParseExpression($"nameof(this.{name})") .WithTriviaFrom(x)); }
private static void UseSetAndRaise(DocumentEditor editor, AccessorDeclarationSyntax setter, AssignmentExpressionSyntax assignment, IMethodSymbol setAndRaise) { var underscoreFields = CodeStyle.UnderscoreFields(editor.SemanticModel); editor.ReplaceNode( setter, x => x.WithBody(null) .WithExpressionBody( SyntaxFactory.ArrowExpressionClause( SyntaxFactory.ParseExpression( $"{(underscoreFields ? string.Empty : "this.")}{setAndRaise.Name}(ref {assignment.Left}, value);"))) .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation)); }
private static async Task <Document> AddInvokerAsync(Document document, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken) .ConfigureAwait(false); var type = editor.SemanticModel.GetDeclaredSymbolSafe(classDeclaration, cancellationToken); var underscoreFields = CodeStyle.UnderscoreFields(editor.SemanticModel); if (type.IsSealed) { editor.AddMethod( classDeclaration, ParseMethod( @" private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }", underscoreFields)); } else if (type.IsStatic) { editor.AddMethod( classDeclaration, ParseMethod( @" private static void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName)); }", underscoreFields)); } else { editor.AddMethod( classDeclaration, ParseMethod( @" protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }", underscoreFields)); } return(editor.GetChangedDocument()); }
private static void MakeWithBackingFieldSet(DocumentEditor editor, PropertyDeclarationSyntax propertyDeclaration, IMethodSymbol setAndRaise, SemanticModel semanticModel) { var classDeclaration = propertyDeclaration.FirstAncestorOrSelf <ClassDeclarationSyntax>(); if (classDeclaration == null) { return; } if (IsSimpleAssignmentOnly(propertyDeclaration, out _, out _, out var assignment, out var fieldAccess)) { var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); var setExpression = SyntaxFactory.ParseExpression($"{(underscoreFields ? string.Empty : "this.")}{setAndRaise.Name}(ref {fieldAccess}, value);") .WithTrailingTrivia(SyntaxFactory.ElasticMarker) .WithSimplifiedNames() .WithAdditionalAnnotations(Formatter.Annotation); editor.ReplaceNode(assignment, setExpression); } }
private static void MakeWithBackingFieldNotify(DocumentEditor editor, PropertyDeclarationSyntax propertyDeclaration, IMethodSymbol invoker, SemanticModel semanticModel, CancellationToken cancellationToken) { var classDeclaration = propertyDeclaration.FirstAncestorOrSelf <ClassDeclarationSyntax>(); if (classDeclaration == null) { return; } if (IsSimpleAssignmentOnly(propertyDeclaration, out var setter, out var statement, out var assignment, out _)) { var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); var property = semanticModel.GetDeclaredSymbolSafe(propertyDeclaration, cancellationToken); var notifyStatement = SyntaxFactory .ParseStatement(Snippet.OnPropertyChanged(invoker, property.Name, underscoreFields)) .WithLeadingTrivia(SyntaxFactory.ElasticMarker) .WithTrailingTrivia(SyntaxFactory.ElasticMarker) .WithSimplifiedNames() .WithAdditionalAnnotations(Formatter.Annotation); if (setter.ExpressionBody != null) { editor.ReplaceNode( setter, (x, _) => { var old = (AccessorDeclarationSyntax)x; return(old.WithBody( SyntaxFactory.Block( SyntaxFactory.ExpressionStatement(assignment), notifyStatement)) .WithExpressionBody(null) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None))); }); editor.FormatNode(propertyDeclaration); } else if (setter.Body != null) { editor.InsertAfter(statement, notifyStatement); editor.FormatNode(propertyDeclaration); } } }
private static void ImplementINotifyPropertyChanged(CodeFixContext context, SemanticModel semanticModel, ClassDeclarationSyntax classDeclaration, DocumentEditor editor) { var type = (ITypeSymbol)semanticModel.GetDeclaredSymbol(classDeclaration, context.CancellationToken); var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); if (!type.Is(KnownSymbol.INotifyPropertyChanged)) { if (classDeclaration.BaseList != null && classDeclaration.BaseList.Types.TryFirst( x => (x.Type as IdentifierNameSyntax)?.Identifier.ValueText.Contains("INotifyPropertyChanged") == true, out var baseType) && context.Diagnostics.Any(IsINotifyPropertyChangedMissing)) { editor.ReplaceNode(baseType, SyntaxFactory.SimpleBaseType(INotifyPropertyChangedType)); } else { editor.AddInterfaceType(classDeclaration, INotifyPropertyChangedType); } } if (!type.TryFindEventRecursive("PropertyChanged", out _)) { editor.AddEvent( classDeclaration, (EventFieldDeclarationSyntax)editor.Generator.EventDeclaration( "PropertyChanged", PropertyChangedEventHandlerType, Accessibility.Public)); } if (!type.TryFindFirstMethodRecursive( "OnPropertyChanged", m => m.Parameters.Length == 1 && m.Parameters[0] .Type == KnownSymbol.String, out _)) { if (type.IsSealed) { editor.AddMethod( classDeclaration, ParseMethod( @"private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }", underscoreFields)); } else { editor.AddMethod( classDeclaration, ParseMethod( @"protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }", underscoreFields)); } } }
private static void MakeWithBackingFieldNotifyWhenValueChanges(DocumentEditor editor, PropertyDeclarationSyntax propertyDeclaration, IMethodSymbol invoker, SemanticModel semanticModel, CancellationToken cancellationToken) { var classDeclaration = propertyDeclaration.FirstAncestorOrSelf <ClassDeclarationSyntax>(); if (classDeclaration == null) { return; } if (propertyDeclaration.TryGetSetter(out var setter)) { if (setter.ExpressionBody != null && IsSimpleAssignmentOnly(propertyDeclaration, out _, out _, out var assignment, out _)) { var property = semanticModel.GetDeclaredSymbolSafe(propertyDeclaration, cancellationToken); var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); var code = StringBuilderPool.Borrow() .AppendLine($"public Type PropertyName") .AppendLine("{") .AppendLine($" get => {assignment.Left};") .AppendLine() .AppendLine(" set") .AppendLine(" {") .AppendLine($" if ({Snippet.EqualityCheck(property.Type, "value", assignment.Left.ToString(), semanticModel)})") .AppendLine(" {") .AppendLine($" return;") .AppendLine(" }") .AppendLine() .AppendLine($" {assignment};") .AppendLine($" {Snippet.OnPropertyChanged(invoker, property.Name, underscoreFields)}") .AppendLine(" }") .AppendLine("}") .Return(); var template = ParseProperty(code); editor.ReplaceNode( setter, (x, _) => { var old = (AccessorDeclarationSyntax)x; return(old.WithBody(template.Setter().Body) .WithExpressionBody(null) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None))); }); editor.FormatNode(propertyDeclaration); } if (setter.Body?.Statements.Count == 1 && IsSimpleAssignmentOnly(propertyDeclaration, out _, out var statement, out assignment, out _)) { var property = semanticModel.GetDeclaredSymbolSafe(propertyDeclaration, cancellationToken); var code = StringBuilderPool.Borrow() .AppendLine($" if ({Snippet.EqualityCheck(property.Type, "value", assignment.Left.ToString(), semanticModel)})") .AppendLine(" {") .AppendLine($" return;") .AppendLine(" }") .AppendLine() .Return(); var ifStatement = SyntaxFactory.ParseStatement(code) .WithSimplifiedNames() .WithLeadingElasticLineFeed() .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation); editor.InsertBefore( statement, ifStatement); var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); var notifyStatement = SyntaxFactory .ParseStatement( Snippet.OnPropertyChanged(invoker, property.Name, underscoreFields)) .WithSimplifiedNames() .WithLeadingElasticLineFeed() .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation); editor.InsertAfter(statement, notifyStatement); editor.FormatNode(propertyDeclaration); } } }
/// <inheritdoc/> public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken) .ConfigureAwait(false); var underscoreFields = CodeStyle.UnderscoreFields(semanticModel); foreach (var diagnostic in context.Diagnostics) { if (diagnostic.Properties.TryGetValue(INPC003NotifyWhenPropertyChanges.PropertyNameKey, out var property)) { var expression = syntaxRoot.FindNode(diagnostic.Location.SourceSpan) .FirstAncestorOrSelf <ExpressionSyntax>(); var typeDeclaration = expression.FirstAncestorOrSelf <TypeDeclarationSyntax>(); var type = semanticModel.GetDeclaredSymbolSafe(typeDeclaration, context.CancellationToken); if (PropertyChanged.TryGetOnPropertyChanged(type, semanticModel, context.CancellationToken, out var invoker) && invoker.Parameters[0].Type == KnownSymbol.String) { var invocation = expression.FirstAncestorOrSelf <InvocationExpressionSyntax>(); var method = (IMethodSymbol)semanticModel.GetSymbolSafe(invocation, context.CancellationToken); if (PropertyChanged.IsSetAndRaise(method, semanticModel, context.CancellationToken) != AnalysisResult.No) { if (invocation.Parent is ExpressionStatementSyntax || invocation.Parent is ArrowExpressionClauseSyntax) { context.RegisterDocumentEditorFix( $"Notify that property {property} changes.", (editor, cancellationToken) => MakeNotifyCreateIf( editor, invocation, property, invoker, underscoreFields), this.GetType(), diagnostic); continue; } if (invocation.Parent is IfStatementSyntax ifStatement) { context.RegisterDocumentEditorFix( $"Notify that property {property} changes.", (editor, _) => MakeNotifyInIf( editor, ifStatement, property, invoker, underscoreFields), this.GetType(), diagnostic); continue; } if (invocation.Parent is PrefixUnaryExpressionSyntax unary && unary.IsKind(SyntaxKind.LogicalNotExpression) && unary.Parent is IfStatementSyntax ifStatement2 && ifStatement2.IsReturnOnly()) { context.RegisterDocumentEditorFix( $"Notify that property {property} changes.", (editor, _) => MakeNotify( editor, expression, property, invoker, underscoreFields), this.GetType(), diagnostic); continue; } } context.RegisterDocumentEditorFix( $"Notify that property {property} changes.", (editor, _) => MakeNotify( editor, expression, property, invoker, underscoreFields), this.GetType(), diagnostic); } } } }