Option Explicit
Dim NomModule As String
Dim NomProc As String
Dim TypeProcedure As vbext_ProcKind
Dim LigneStartProc As Long
Dim LigneBodyProc As Long
Dim NbLignesProc As Long
Dim CodeContenu As String
Dim CodeDeclaration As String
Dim ListeVar As String
Dim NbInutile As Integer
Const TAG = " ' VARIABLE INUTILE"
Dim CommentaireTag As String
Sub AnalyseCodeVarInu()
' Fonctionne pour les Variables déclarées Dim dans le Module
'  Il y aura besoin de code supplémentaire pour des variables :
'       Public, Private, Static, Array, Type ... End Type
Dim Titre As String
Dim Message As String
Dim Debut As Long
Dim ObjModule As Object
Dim TagLigne As Boolean ' on marque les lignes Dim inutilisée
    Titre = "Analyse des Variables inutilisées"
    Message = "Souhaitez-vous ajouter le commentaire <" & TAG & "> sur la ligne de déclaration des variables inutilisées ?"
    If MsgBox(Message, vbYesNo, Titre) = vbYes Then
        TagLigne = True
        CommentaireTag = TAG & " (" & Now & ")"
    Else
        TagLigne = False
    End If
    NbInutile = 0
    ' pour le moment sur le classeur courant
    For Each ObjModule In ThisWorkbook.VBProject.VBComponents
        NomModule = ObjModule.Name
        ' pour le moment test sur seulement un module
        If NomModule = "Module6_PourVar" Then
        
            With ThisWorkbook.VBProject.VBComponents(NomModule).CodeModule
            
                'Déclarations du Module
                '----------------------
                ListeVar = ""
                NomProc = ""
                LigneStartProc = 1
                LigneBodyProc = 1
                NbLignesProc = .CountOfLines - .CountOfDeclarationLines
                NbLignesProc = .CountOfDeclarationLines
                If NbLignesProc > 0 Then
                    CodeDeclaration = .Lines(1, .CountOfDeclarationLines) & vbLf
                    CodeContenu = .Lines(1, .CountOfLines)
                    AnalyseCodeVarInuProcedure CodeDeclaration, CodeContenu, TagLigne
                End If
                Debug.Print "========================================================="
                Debug.Print "   Analyse du Module " & NomModule
                Debug.Print "========================================================="
                Debug.Print ListeVar
                Debug.Print
                
                ' Chaque Procédure
                '-----------------
                'positionnement après la partie Déclarations du Module
                ListeVar = ""
                Debut = .CountOfDeclarationLines + 1
                Do Until Debut >= .CountOfLines
                    NomProc = .ProcOfLine(Debut, TypeProcedure)
                    LigneStartProc = .ProcStartLine(NomProc, TypeProcedure)
                    LigneBodyProc = .ProcBodyLine(NomProc, TypeProcedure)
                    NbLignesProc = .ProcCountLines(NomProc, TypeProcedure)
                    CodeContenu = .Lines(LigneBodyProc, NbLignesProc)
                    AnalyseCodeVarInuProcedure CodeContenu, CodeContenu, TagLigne
                    If ListeVar <> "" Then
                        Debug.Print "--- Procédure " & NomProc & " ---"
                        Debug.Print ListeVar
                        Debug.Print
                    End If
                    ListeVar = ""
                    Debut = Debut + NbLignesProc + 1
                Loop
            End With
            
            Message = "Analyse du Code du Module " & NomModule & " terminée." & vbLf _
                    & NbInutile & " variables inutilisées." & vbLf _
                    & "Voir la fenêtre Exécution de l'éditeur VBE"
                    
            MsgBox Message, vbInformation, Titre
            
        End If
    Next ObjModule
End Sub
Sub AnalyseCodeVarInuProcedure(pCodeDeclaration As String, pCodeContenu As String, pTagLigne As Boolean)
Dim TabCodeDeclaration() As String
Dim TabCodeContenu() As String
Dim ChaineDim As String
Dim TabDim() As String, ITabDim As Integer, ITabDim2 As Integer, ITabDim3 As Integer
Dim TabDimEc() As String, ITabDimEc As Integer
Dim ChaineSansCom As String
Dim TabSansCom() As String, ITabSansCom As Integer
Dim i As Integer, j As Integer
Dim LigneCourante As String
Dim Quoi As String
Dim ChaineW As String
Dim Pos As Long, Pos2 As Long
Dim Absent As Boolean
Dim Fin As Boolean
    ' Analyse variables (pour le moment seulement Dim)
    '-------------------
    Quoi = "Dim "
    
    ' Préparation des tableaux de déclarations Dim
    '---------------------------------------------
    TabCodeDeclaration = Split(pCodeDeclaration, vbLf)
    'la dernière ligne est vide
    ReDim Preserve TabCodeDeclaration(0 To UBound(TabCodeDeclaration) - 1)
    For i = LBound(TabCodeDeclaration) To UBound(TabCodeDeclaration)
        ' on enlève les espaces de gauche
        TabCodeDeclaration(i) = LTrim(TabCodeDeclaration(i))
        ' on enlève le saut de ligne
        If Len(TabCodeDeclaration(i)) <> 0 And i < UBound(TabCodeDeclaration) - LBound(TabCodeDeclaration) Then
            TabCodeDeclaration(i) = Left(TabCodeDeclaration(i), Len(TabCodeDeclaration(i)) - 1)
        End If
        If Left(LTrim(TabCodeDeclaration(i)), Len(Quoi)) = Quoi Then
            ' Ligne Dim
            If ChaineDim = "" Then ChaineDim = TabCodeDeclaration(i) Else ChaineDim = ChaineDim & vbLf & TabCodeDeclaration(i)
        End If
    Next i
    
    ' TabDim contient toutes les déclarations Dim : "Dim xxx"
    TabDim = Split(ChaineDim, vbLf)
    
    ' Tableau du contenu sans les lignes commentaires
    '---------------------------------------------
    ChaineSansCom = ""
    TabCodeContenu = Split(pCodeContenu, vbLf)
    For i = LBound(TabCodeContenu) To UBound(TabCodeContenu)
        If Len(TabCodeContenu(i)) <> 0 And i <> UBound(TabCodeContenu) Then TabCodeContenu(i) = Left(TabCodeContenu(i), Len(TabCodeContenu(i)) - 1)
        If Left(LTrim(TabCodeContenu(i)), Len(Quoi)) <> Quoi Then
            If Left(LTrim(TabCodeContenu(i)), 1) <> "'" Then
                If ChaineSansCom = "" Then ChaineSansCom = TabCodeContenu(i) Else ChaineSansCom = ChaineSansCom & vbLf & TabCodeContenu(i)
            End If
        End If
    Next i
    ' élimination des lignes commentaires
    TabSansCom = Split(ChaineSansCom, vbLf)
  
    ' Contrôle du contenu à partir des tableaux de déclarations Dim
    '--------------------------------------------------------------
    For ITabDim = LBound(TabDim) To UBound(TabDim)
        ' ici on éclate les déclarations multiples sur la même e lignes en x lignes
        TabDimEc = Filter(Split(Replace(Replace(TabDim(ITabDim), "Dim ", "Dim µ"), ", ", " µ")), "µ")
        For i = LBound(TabDimEc) To UBound(TabDimEc)
            TabDimEc(i) = Replace(TabDimEc(i), "µ", "")
            If InStr(TabDimEc(i), "(") <> 0 Then
                ' Variable Tableau
                '   -> pour détecter l'utilisation dans le code on testera "Var(" et "Var"
                TabDimEc(i) = Left(TabDimEc(i), InStr(TabDimEc(i), "(") - 1)
            End If
        Next i
        
        For ITabDimEc = LBound(TabDimEc) To UBound(TabDimEc)
            Absent = True
            For ITabSansCom = LBound(TabSansCom) To UBound(TabSansCom)
                Pos = InStr(TabSansCom(ITabSansCom), TabDimEc(ITabDimEc))
                If InStr(TabSansCom(ITabSansCom), TabDimEc(ITabDimEc)) <> 0 Then
                    ' on regarde si trouvé en dehors d'une zone commentaire
                    Pos2 = InStr(TabSansCom(ITabSansCom), "'")
                    If Pos2 <> 0 Then
                        ' on a trouvé le caractère ' sur la ligne
                        If ChaineEntreApostrophes(TabSansCom(ITabSansCom), "'") Then
                            'le caractère ' est encadré par "" -> ce n'est donc pas commentaire
                            'on regarde si variable encadré par ""
                            Absent = ChaineEntreApostrophes(TabSansCom(ITabSansCom), TabDimEc(ITabDimEc))
                            If Not Absent Then Exit For
                        End If
                    Else
                        'pas dans zone commentaire
                        'on regarde si encadré par ""
                        Absent = ChaineEntreApostrophes(TabSansCom(ITabSansCom), TabDimEc(ITabDimEc))
                        If Not Absent Then Exit For
                    End If
                End If
            Next ITabSansCom
            If Absent Then
                NbInutile = NbInutile + 1
                If pTagLigne Then
                    ' on marque la ligne Dim inutilisée
                    ' recherche du n° d'item de TabDim à partir de TabDimEc
                    For ITabDim2 = LBound(TabDim) To UBound(TabDim)
                        If InStr(TabDim(ITabDim2), TabDimEc(ITabDimEc)) <> 0 Then
                            ITabDim3 = ITabDim2
                            Exit For
                        End If
                    Next ITabDim2
                    
                    For i = LigneBodyProc To LigneBodyProc + NbLignesProc - 1
                        LigneCourante = ThisWorkbook.VBProject.VBComponents(NomModule).CodeModule.Lines(i, 1)
                        If TabDim(ITabDim3) = LTrim(LigneCourante) Then
                            ' on ajoute le tag
                            ' on remplace le Tag précédent s'il existe
                            Pos = InStr(LigneCourante, TAG)
                            If Pos <> 0 Then
                                LigneCourante = Left(LigneCourante, Pos - 1)
                            End If
                            LigneCourante = LigneCourante & CommentaireTag & " ---> " & TabDimEc(ITabDimEc)
                            ThisWorkbook.VBProject.VBComponents(NomModule).CodeModule.ReplaceLine Line:=i, String:=LigneCourante
                            Exit For
                        End If
                    Next i
                End If
                ' pour le Debug.Print
                If ListeVar = "" Then ListeVar = ListeVar & TabDimEc(ITabDimEc) Else ListeVar = ListeVar & vbLf & TabDimEc(ITabDimEc)
            End If
        Next ITabDimEc
    Next ITabDim
End Sub
Function ChaineEntreApostrophes(pChaine As String, pSousChaine As String) As Boolean
    'on regarde si encadré par ""
    Dim ChaineW As String
    Dim Pos As Long, Pos2 As Long
    Dim Absent As Boolean
    Dim Fin As Boolean
    ChaineEntreApostrophes = True
    
    Pos = InStr(pChaine, pSousChaine)
    Pos2 = InStr(pChaine, """")
    If Pos2 < Pos Then
        ' le premier " est situé avant, on va regarder où est le suivant
        ChaineW = Mid(pChaine, Pos2 + 1, Len(pChaine))
        Fin = False
        While Not Fin
            Pos = InStr(ChaineW, pSousChaine)
            Pos2 = InStr(ChaineW, """")
            Select Case True
                Case Pos = 0
                    ' Var non trouvée hors zone commentaire
                    Fin = True
                Case Pos2 = 0 And Pos <> 0
                    ' pas de 2ème "
                    ChaineEntreApostrophes = False
                    Fin = True
                Case Else
                    ' 2ème " avant ou après la variable -> on avance
                    ChaineW = Mid(ChaineW, Pos2 + 1, Len(ChaineW))
            End Select
        Wend
    Else
        ChaineEntreApostrophes = False
    End If
End Function