|
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.Shared.Helpers.RemoveUnnecessaryImports
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryImports
Partial Friend Class VisualBasicRemoveUnnecessaryImportsRewriter
Inherits VisualBasicSyntaxRewriter
Private ReadOnly _unnecessaryImports As ISet(Of ImportsClauseSyntax)
Private ReadOnly _cancellationToken As CancellationToken
Private ReadOnly _annotation As New SyntaxAnnotation()
Public Sub New(unnecessaryImports As ISet(Of ImportsClauseSyntax),
cancellationToken As CancellationToken)
_unnecessaryImports = unnecessaryImports
_cancellationToken = cancellationToken
End Sub
Public Shared Function RemoveUnnecessaryImports(
root As CompilationUnitSyntax,
importsClause As ImportsClauseSyntax,
cancellationToken As CancellationToken) As CompilationUnitSyntax
Dim newRoot = New VisualBasicRemoveUnnecessaryImportsRewriter(New HashSet(Of ImportsClauseSyntax) From {importsClause}, cancellationToken).Visit(root)
Return DirectCast(newRoot, CompilationUnitSyntax)
End Function
Public Overrides Function DefaultVisit(node As SyntaxNode) As SyntaxNode
_cancellationToken.ThrowIfCancellationRequested()
Return MyBase.DefaultVisit(node)
End Function
Public Overrides Function VisitImportsStatement(node As ImportsStatementSyntax) As SyntaxNode
If Not node.ImportsClauses.All(AddressOf _unnecessaryImports.Contains) Then
Return node.RemoveNodes(node.ImportsClauses.Where(AddressOf _unnecessaryImports.Contains), SyntaxRemoveOptions.KeepNoTrivia)
Else
Return node.WithAdditionalAnnotations(_annotation)
End If
End Function
Private Function ProcessImports(compilationUnit As CompilationUnitSyntax) As CompilationUnitSyntax
Dim oldImports = compilationUnit.Imports.ToList()
Dim firstImportNotBeingRemoved = True
Dim passedLeadingTrivia = False
Dim remainingTrivia As SyntaxTriviaList = Nothing
For i = 0 To oldImports.Count - 1
Dim oldImport = oldImports(i)
If oldImport.HasAnnotation(_annotation) Then
' Found a node we marked to delete. Remove it.
oldImports(i) = Nothing
Dim leadingTrivia = oldImport.GetLeadingTrivia()
If ShouldPreserveTrivia(leadingTrivia) Then
' This import had trivia we want to preserve. If we're the last import,
' then copy this trivia out so that our caller can place it on the next token.
' If there is any import following us, then place it on that.
If i < oldImports.Count - 1 Then
Dim nextIndex = i + 1
Dim nextImport = oldImports(nextIndex)
If ShouldPreserveTrivia(nextImport.GetLeadingTrivia()) Then
' If we need to preserve the next trivia too then, prepend
' the two together.
oldImports(nextIndex) = nextImport.WithPrependedLeadingTrivia(leadingTrivia)
Else
' Otherwise, replace the next trivia with this trivia that we
' want to preserve.
oldImports(nextIndex) = nextImport.WithLeadingTrivia(leadingTrivia)
End If
passedLeadingTrivia = True
Else
remainingTrivia = leadingTrivia
End If
End If
If i > 0 Then
' We should replace the trailing trivia of the previous import
' with the trailing trivia of this import.
Dim index = i - 1
Dim previousImport = oldImports(index)
If previousImport Is Nothing AndAlso index > 0 Then
index -= 1
previousImport = oldImports(index)
End If
If previousImport IsNot Nothing Then
Dim trailingTrivia = oldImport.GetTrailingTrivia()
oldImports(index) = previousImport.WithTrailingTrivia(trailingTrivia)
End If
End If
ElseIf firstImportNotBeingRemoved Then
' 1) We only apply this logic for Not first using, that is saved:
' ===================
' #Const A = 1
'
' Imports System <- if we save this import, we don't need to cut leading lines
' ===================
' 2) If leading trivia was saved from the previous import, that was removed,
' we don't bother cutting blank lines as well:
' ===================
' #Const A = 1
'
' Imports System <- need to delete this import
' Imports System.Collections.Generic <- this import is saved, no need to eat the line,
' otherwise https://github.com/dotnet/roslyn/issues/58972 will happen
If i > 0 AndAlso Not passedLeadingTrivia Then
Dim currentImport = oldImports(i)
Dim currentImportLeadingTrivia = currentImport.GetLeadingTrivia()
oldImports(i) = currentImport.WithLeadingTrivia(currentImportLeadingTrivia.WithoutLeadingWhitespaceOrEndOfLine())
End If
firstImportNotBeingRemoved = False
End If
Next
Dim newImports = SyntaxFactory.List(oldImports.WhereNotNull())
If remainingTrivia.Count > 0 Then
Dim nextToken = compilationUnit.Imports.Last().GetLastToken().GetNextTokenOrEndOfFile()
compilationUnit = compilationUnit.ReplaceToken(nextToken, nextToken.WithPrependedLeadingTrivia(remainingTrivia))
End If
Return compilationUnit.WithImports(newImports)
End Function
Private Shared Function ShouldPreserveTrivia(trivia As SyntaxTriviaList) As Boolean
Return trivia.Any(Function(t) Not t.IsWhitespaceOrEndOfLine())
End Function
Public Overrides Function VisitCompilationUnit(node As CompilationUnitSyntax) As SyntaxNode
Dim compilationUnit = DirectCast(MyBase.VisitCompilationUnit(node), CompilationUnitSyntax)
If Not compilationUnit.Imports.Any(Function(i) i.HasAnnotation(_annotation)) Then
Return compilationUnit
End If
Dim newCompilationUnit = ProcessImports(compilationUnit)
If newCompilationUnit.Imports.Count = 0 AndAlso newCompilationUnit.Options.Count = 0 Then
If newCompilationUnit.Attributes.Count > 0 OrElse newCompilationUnit.Members.Count > 0 Then
Dim firstToken = newCompilationUnit.GetFirstToken()
Dim newFirstToken = RemoveUnnecessaryImportsHelpers.StripNewLines(VisualBasicSyntaxFacts.Instance, firstToken)
newCompilationUnit = newCompilationUnit.ReplaceToken(firstToken, newFirstToken)
End If
End If
Return newCompilationUnit
End Function
End Class
End Namespace
|