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 MakeNotify(DocumentEditor editor, ExpressionSyntax assignment, string propertyName, IMethodSymbol invoker, bool usesUnderscoreNames) { var snippet = assignment.FirstAncestor <PropertyDeclarationSyntax>() is PropertyDeclarationSyntax propertyDeclaration && propertyDeclaration.Identifier.ValueText == propertyName ? Snippet.OnPropertyChanged(invoker, propertyName, usesUnderscoreNames) : Snippet.OnOtherPropertyChanged(invoker, propertyName, usesUnderscoreNames); var onPropertyChanged = SyntaxFactory.ParseStatement(snippet) .WithSimplifiedNames() .WithLeadingElasticLineFeed() .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation); if (assignment.Parent is AnonymousFunctionExpressionSyntax anonymousFunction) { if (anonymousFunction.Body is BlockSyntax block) { if (block.Statements.Count > 1) { var previousStatement = InsertAfter(block, block.Statements.Last(), invoker); editor.InsertAfter(previousStatement, new[] { onPropertyChanged }); } return; } var expressionStatement = (ExpressionStatementSyntax)editor.Generator.ExpressionStatement(anonymousFunction.Body); var withStatements = editor.Generator.WithStatements(anonymousFunction, new[] { expressionStatement, onPropertyChanged }); editor.ReplaceNode(anonymousFunction, withStatements); } else if (assignment.Parent is ExpressionStatementSyntax assignStatement && assignStatement.Parent is BlockSyntax assignBlock) { var previousStatement = InsertAfter(assignBlock, assignStatement, invoker); editor.InsertAfter(previousStatement, new[] { onPropertyChanged }); }
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); foreach (var diagnostic in context.Diagnostics) { var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); if (string.IsNullOrEmpty(token.ValueText)) { continue; } var ifStatement = syntaxRoot.FindNode(diagnostic.Location.SourceSpan) .FirstAncestorOrSelf <IfStatementSyntax>(); if (!IsIfReturn(ifStatement)) { continue; } var setter = ifStatement.FirstAncestorOrSelf <AccessorDeclarationSyntax>(); if (setter?.IsKind(SyntaxKind.SetAccessorDeclaration) != true) { continue; } var propertyDeclaration = setter.FirstAncestorOrSelf <PropertyDeclarationSyntax>(); var property = semanticModel.GetDeclaredSymbolSafe(propertyDeclaration, context.CancellationToken); if (property == null) { continue; } if (!Property.TryGetBackingFieldFromSetter(property, semanticModel, context.CancellationToken, out var backingField)) { continue; } if (Property.TryFindValue(setter, semanticModel, context.CancellationToken, out var value)) { if (CanFix(ifStatement, semanticModel, context.CancellationToken, value, backingField) || CanFix(ifStatement, semanticModel, context.CancellationToken, value, property)) { var fieldAccess = backingField.Name.StartsWith("_") ? backingField.Name : $"this.{backingField.Name}"; var equalsExpression = SyntaxFactory.ParseExpression( Snippet.EqualityCheck( property.Type, "value", fieldAccess, semanticModel)) .WithSimplifiedNames(); context.RegisterDocumentEditorFix( $"Use {equalsExpression}", (editor, cancellationToken) => editor.ReplaceNode(ifStatement.Condition, equalsExpression), this.GetType(), diagnostic); } } } }