<commit_before>﻿namespace WebSharper.CSharp.Analyzer

open System
open System.Collections.Generic
open System.Collections.Immutable
open System.Linq
open System.Threading
open System.Reflection
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CSharp
open Microsoft.CodeAnalysis.CSharp.Syntax
open Microsoft.CodeAnalysis.Diagnostics
open System.IO
open WebSharper.Compiler

[<DiagnosticAnalyzer(LanguageNames.CSharp)>]
type WebSharperCSharpAnalyzer () =
    inherit DiagnosticAnalyzer()
   
    static let wsWarning = 
        new DiagnosticDescriptor ("WebSharperWarning", "WebSharper warnings", "{0}", "WebSharper", DiagnosticSeverity.Warning, true, null, null)

    static let wsError = 
        new DiagnosticDescriptor ("WebSharperError", "WebSharper errors", "{0}", "WebSharper", DiagnosticSeverity.Error, true, null, null)

    let cachedRefMeta = Dictionary() 

    member this.GetRefMeta(path) =
        lock cachedRefMeta <| fun () ->
        match cachedRefMeta.TryGetValue path with
        | true, res -> res
        | _ ->
            let mutable err = None
            let m = 
                try WebSharper.Compiler.FrontEnd.ReadFromFile(path)
                with e ->
                    err <- Some e.Message
                    None

            let watcher = new FileSystemWatcher(Path.GetDirectoryName path, Path.GetFileName path)
            let onChange (_: FileSystemEventArgs) =
                lock cachedRefMeta <| fun () ->
                cachedRefMeta.Remove(path) |> ignore
                watcher.Dispose() 

            watcher.Changed |> Event.add onChange
            watcher.Renamed |> Event.add onChange
            watcher.Deleted |> Event.add onChange

            cachedRefMeta.Add(path, (m, err))    
            m, err        

    override this.SupportedDiagnostics =
        ImmutableArray.Create(wsWarning, wsError)

    override this.Initialize(initCtx) =
        initCtx.RegisterCompilationStartAction(fun startCtx ->
            let compilation = startCtx.Compilation :?> CSharpCompilation

            let refPaths =
                compilation.ExternalReferences |> Seq.choose (fun r -> 
                    match r with
                    | :? PortableExecutableReference as cr -> Some cr.FilePath
                    | _ -> None
                )
                |> List.ofSeq
                
            let metas = refPaths |> List.map this.GetRefMeta
            let errors = metas |> List.choose snd

            let refMeta =
                if List.isEmpty metas || not (List.isEmpty errors) then None 
                else Some (WebSharper.Core.DependencyGraph.Graph.UnionOfMetadata (metas |> List.choose fst))

            startCtx.RegisterCompilationEndAction(fun endCtx ->
                if not (List.isEmpty errors) then
                    for err in errors do    
                        endCtx.ReportDiagnostic(Diagnostic.Create(wsError, Location.None, err))
                else
                try
                    let compilation = endCtx.Compilation :?> CSharpCompilation

                    let compiler = WebSharper.Compiler.CSharp.WebSharperCSharpCompiler(ignore)

                    let referencedAsmNames =
                        refPaths
                        |> Seq.map (fun i -> 
                            let n = Path.GetFileNameWithoutExtension(i)
                            n, i<commit_msg>C# analyzer disposes watchers, does not run WS compilation if there are C# errors<commit_after>﻿namespace WebSharper.CSharp.Analyzer

open System
open System.Collections.Generic
open System.Collections.Immutable
open System.Linq
open System.Threading
open System.Reflection
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CSharp
open Microsoft.CodeAnalysis.CSharp.Syntax
open Microsoft.CodeAnalysis.Diagnostics
open System.IO
open WebSharper.Compiler

[<DiagnosticAnalyzer(LanguageNames.CSharp)>]
type WebSharperCSharpAnalyzer () =
    inherit DiagnosticAnalyzer()
   
    static let wsWarning = 
        new DiagnosticDescriptor ("WebSharperWarning", "WebSharper warnings", "{0}", "WebSharper", DiagnosticSeverity.Warning, true, null, null)

    static let wsError = 
        new DiagnosticDescriptor ("WebSharperError", "WebSharper errors", "{0}", "WebSharper", DiagnosticSeverity.Error, true, null, null)

    let cachedRefMeta = Dictionary() 

    member this.GetRefMeta(path) =
        lock cachedRefMeta <| fun () ->
        match cachedRefMeta.TryGetValue path with
        | true, (m, err, _) -> m, err
        | _ ->
            let mutable err = None
            let m = 
                try WebSharper.Compiler.FrontEnd.ReadFromFile(path)
                with e ->
                    err <- Some e.Message
                    None

            let watcher = new FileSystemWatcher(Path.GetDirectoryName path, Path.GetFileName path)
            let onChange (_: FileSystemEventArgs) =
                lock cachedRefMeta <| fun () ->
                cachedRefMeta.Remove(path) |> ignore
                watcher.Dispose() 

            watcher.Changed |> Event.add onChange
            watcher.Renamed |> Event.add onChange
            watcher.Deleted |> Event.add onChange

            cachedRefMeta.Add(path, (m, err, watcher))    
            m, err        

    interface IDisposable with
        member this.Dispose() =
            lock cachedRefMeta <| fun () ->
                for _, _, watcher in cachedRefMeta.Values do
                    watcher.Dispose()
                cachedRefMeta.Clear()

    override this.SupportedDiagnostics =
        ImmutableArray.Create(wsWarning, wsError)

    override this.Initialize(initCtx) =
        initCtx.RegisterCompilationStartAction(fun startCtx ->
            let compilation = startCtx.Compilation :?> CSharpCompilation

            let refPaths =
                compilation.ExternalReferences |> Seq.choose (fun r -> 
                    match r with
                    | :? PortableExecutableReference as cr -> Some cr.FilePath
                    | _ -> None
                )
                |> List.ofSeq
                
            let metas = refPaths |> List.map this.GetRefMeta
            let errors = metas |> List.choose snd

            let refMeta =
                if List.isEmpty metas || not (List.isEmpty errors) then None 
                else Some (WebSharper.Core.DependencyGraph.Graph.UnionOfMetadata (metas |> List.choose fst))

            startCtx.RegisterCompilationEndAction(fun endCtx ->
                if not (List.isEmpty errors) then
                    for err in errors do    
                        endCtx.ReportDiagnostic(Diagnostic.Create(wsError, Location.None, err))
                else
                try
                    let compilation = endCtx.Compilation :?> CSharpCompilation

                    if compilation.GetDiagnostics() |> Seq.exists (fun d -> d.Severity = DiagnosticSeverity.Error) then () else

                    let compiler = WebSharper.Compiler.CSharp.WebSharperCSharpCompiler(ignore)

                    let referencedAsmNames =
                        refPaths
                        |> Seq.map (fun i -> 
                            let n = Path.GetFileNameWithoutExtension(i)
                            n, i
{'fraction_non_alphanumeric': 0.0776766004415011, 'fraction_numerical': 0.0005518763796909492, 'mean_word_length': 1.8449764521193093, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6955100', 'n_tokens_mistral': 1925, 'n_tokens_neox': 1811, 'n_words': 453}