Subscribe to The Madman Scribblings        RSS Feed

Improving the Code Smell.

Icon Leave Comment
Improving the Code Smell.

Fixed: Now it respects and preserves any trailing comments after the code, controlled by the If Statement.
Fixed: Correctly detects the number of lines of code inside the block.

Diagnostic Analyzer
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.Diagnostics

<ExportDiagnosticAnalyzer(DiagnosticAnalyzer.DiagnosticId, LanguageNames.VisualBasic)>
Public Class DiagnosticAnalyzer
  Implements ISyntaxNodeAnalyzer(Of SyntaxKind)

  Friend Const DiagnosticId  = "CodeSmell - Transform into single line if statement."
  Friend Const Description   = "If (condtion) Then ...  End If."
  Friend Const MessageFormat = "Replace with single line If (condition) Then ... statement."
  Friend Const Category      = "Syntax"

  Friend Shared Rule As New DiagnosticDescriptor(DiagnosticId, Description, MessageFormat, Category, DiagnosticSeverity.Warning)

    Public ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) Implements IDiagnosticAnalyzer.SupportedDiagnostics
            Return ImmutableArray.Create(Rule)
        End Get
    End Property

  Public ReadOnly Property SyntaxKindsOfInterest As ImmutableArray(Of SyntaxKind) Implements ISyntaxNodeAnalyzer(Of SyntaxKind).SyntaxKindsOfInterest
      Return ImmutableArray.Create(SyntaxKind.MultiLineIfBlock)
    End Get
  End Property

  Public Sub AnalyzeNode(node As SyntaxNode, semanticModel As SemanticModel, addDiagnostic As Action(Of Diagnostic), cancellationToken As CancellationToken) Implements ISyntaxNodeAnalyzer(Of SyntaxKind).AnalyzeNode
    Dim thisNode = DirectCast(node, MultiLineIfBlockSyntax)
    ' Does it have any ElseIf parts? If so then ignore this node.
    If thisNode.ElseIfParts.Count > 0  Then Return
    ' Does it have an Else part? If so then ignore this node.
    If thisNode.ElsePart IsNot Nothing Then Return
    ' Does the IfPart have a comment after it? If so ignore this node.   
    If thisNode.HasTrailingTrivia AndAlso thisNode.GetTrailingTrivia.Any(Function(n) n.IsKind(SyntaxKind.CommentTrivia)) Then Return
    Dim ifline = thisNode.SyntaxTree.GetLineSpan(thisNode.Span)
    Dim delta  = (ifline.EndLinePosition.Line - ifline.StartLinePosition.Line) - 1
    Dim s = thisNode.IfPart.Statements
    If delta <> 1 Then Return   
    Dim ThenPart = thisNode.IfPart.DescendantNodesAndTokens.FirstOrDefault(Function(n) n.IsKind(SyntaxKind.ThenKeyword ))
    If ThenPart.IsMissing  Then Return
    If ThenPart.HasTrailingTrivia AndAlso ThenPart.GetTrailingTrivia.Any(Function(n) n.IsKind(SyntaxKind.CommentTrivia )) Then Return 
    If s.Count = 1 Then
      Dim diag = Diagnostic.Create(Rule, Location.Create(node.SyntaxTree, TextSpan.FromBounds(thisNode.FullSpan.Start, thisNode.FullSpan.End)), node)
    End If
  End Sub
End Class

Code-Fix Provider

Option Strict On
Imports Microsoft.CodeAnalysis.Rename
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis

<ExportCodeFixProvider(DiagnosticAnalyzer.DiagnosticId, LanguageNames.VisualBasic)>
Friend Class CodeFixProvider
  Implements ICodeFixProvider

  Public Function GetFixableDiagnosticIds() As IEnumerable(Of String) Implements ICodeFixProvider.GetFixableDiagnosticIds
    Return {DiagnosticAnalyzer.DiagnosticId}
  End Function

  Public Async Function GetFixesAsync(document As Document, span As TextSpan, diagnostics As IEnumerable(Of Diagnostic), cancellationToken As CancellationToken) As Task(Of IEnumerable(Of CodeAction)) Implements ICodeFixProvider.GetFixesAsync
    Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
    Dim diagnosticSpan = diagnostics.First().Location.SourceSpan
    Dim declaration = root.FindToken(diagnosticSpan.Start).Parent
    Return {CodeAction.Create("Make Single Line IF Statement", Function(c) MakeSingleLineIfStatementAsync(document, declaration, c))}
  End Function

  Private Async Function MakeSingleLineIfStatementAsync(doc As Document, typeStmt As SyntaxNode, cancellationToken As CancellationToken) As Task(Of Document)
    Dim MutlilineIfEndIfStatement = DirectCast(typeStmt.Parent.Parent, MultiLineIfBlockSyntax)
    If MutlilineIfEndIfStatement Is Nothing Then Return Nothing
    Dim statement = MutlilineIfEndIfStatement.IfPart.Statements(0)
    Dim SingleLineIfStatement = SyntaxFactory.SingleLineIfStatement( 
                                  SyntaxFactory.SingleLineIfPart( MutlilineIfEndIfStatement.IfPart.Begin,
                                                                ).WithTrailingTrivia(statement.GetTrailingTrivia.Add( SyntaxFactory.EndOfLineTrivia(""))))
    Dim root = Await doc.GetSyntaxRootAsync(cancellationToken)
    Dim newRoot = root.ReplaceNode(Of SyntaxNode)(MutlilineIfEndIfStatement, SingleLineIfStatement).NormalizeWhitespace
    Dim newDoc = doc.WithSyntaxRoot(newRoot)
    Return newDoc
  End Function

End Class

Available to download and test out via the Visual Studio Gallery (Code Smell) Single Statement If Block

0 Comments On This Entry


Search My Blog

Recent Entries

Recent Comments