<commit_before>namespace FsAutoComplete

open System
open System.IO
open Microsoft.FSharp.Compiler.SourceCodeServices
open FSharpLint.Application
open FsAutoComplete.UnopenedNamespacesResolver
open FsAutoComplete.UnionPatternMatchCaseGenerator
open Microsoft.FSharp.Compiler


module Response = CommandResponse

type Commands (serialize : Serializer) =
    let checker = FSharpCompilerServiceChecker()
    let state = FsAutoComplete.State.Initial
    let fsharpLintConfig = ConfigurationManager.ConfigurationManager()

    member private __.SerializeResultAsync (successToString: Serializer -> 'a -> Async<string>, ?failureToString: Serializer -> string -> string) =
        Async.bind <| function
            // A failure is only info here, as this command is expected to be
            // used 'on idle', and frequent errors are expected.
            | Result.Failure e -> async.Return [(defaultArg failureToString Response.info) serialize e]
            | Result.Success r -> successToString serialize r |> Async.map List.singleton

    member private x.SerializeResult (successToString: Serializer -> 'a -> string, ?failureToString: Serializer -> string -> string) =
        x.SerializeResultAsync ((fun s x -> successToString s x |> async.Return), ?failureToString = failureToString)

    member __.TryGetRecentTypeCheckResultsForFile = checker.TryGetRecentCheckResultsForFile
    member __.TryGetFileCheckerOptionsWithLinesAndLineStr = state.TryGetFileCheckerOptionsWithLinesAndLineStr
    member __.TryGetFileCheckerOptionsWithLines = state.TryGetFileCheckerOptionsWithLines
    member __.Files = state.Files

    member __.Parse file lines version = async {
        let colorizations = state.ColorizationOutput
        let parse' fileName text options =
            async {
                let! result = checker.ParseAndCheckFileInProject(fileName, version, text, options)
                return
                    match result with
                    | Failure e -> [Response.error serialize e]
                    | Success (parseResult, checkResults) ->
                        match checkResults with
                        | FSharpCheckFileAnswer.Aborted -> [Response.info serialize "Parse aborted"]
                        | FSharpCheckFileAnswer.Succeeded results ->
                            let errors = Array.append results.Errors parseResult.Errors
                            if colorizations then
                                [ Response.errors serialize (errors, fileName)
                                  Response.colorizations serialize (results.GetExtraColorizationsAlternate()) ]
                            else [ Response.errors serialize (errors, fileName) ]
            }
        let file = Path.GetFullPath file
        let text = String.concat "\n" lines

        if Utils.isAScript file then
            let! checkOptions = checker.GetProjectOptionsFromScript(file, text)
            state.AddFileTextAndCheckerOptions(file, lines, checkOptions)
            return! parse' file text checkOptions
        else
            let checkOptions = state.GetCheckerOptions(file, lines)
            return! parse' file text checkOptions
    }

    member __.ParseAndCheckProjectsInBackgroundForFile file = async {
        do checker.CheckProjectsInBackgroundForFile (file, state.FileCheckOptions.ToSeq() )
        return [Response.errors serialize ([||], "") ]
    }

    member __.ParseProjectsForFile file = async {
        let! res = checker.ParseProjectsForFile(file, state.FileCheckOptions.ToSeq())
        return
            match res with
            | Failure e -> [Response.error serialize e]
            | Success results ->
                let errors = results |> Array.collect (fun r -> r.Errors)
                [ Response.errors serialize (errors, file)]
    }

    // member __.FileChecked =
    //     checker.FileChecked
    //     |> Event.map (fun fn ->
    //         let file = Path.GetFullPath fn
    //         let res = state.FileCheckOptions |> Seq.tryFind (fun kv -> Path.GetFullPath kv.Key = file)
    //         match res with
    //         | None  -> async { return [Response.info serialize ( sprintf "Project for file not found: %s" file) ]  }
    //         | Some kv ->
    //             async {
    //                 let result= checker.TryGetRecentCheckResultsForFile(fn, kv.Value)
    //                 return
    //                     match result with
    //                     | None -> [Response.info serialize "File not parsed"]
    //                     | Some res -> [ Response.errors serialize (res.GetCheckResults.Errors, file) ]
    //             })

    member __.Project projectFileName verbose onChange = async {
        let projectFileName = Path.GetFullPath projectFileName
        let project = state.Projects.TryFind projectFileName

        let project = project |> Option.getOrElseFun (fun _ ->
            let project = new Project(projectFileName, onChange)
            state.Projects.[projectFileName] <- project
            project)

        let (|NetCore|Net45|Unsupported|) file =
            //.NET Core Sdk preview3+ replace project.json with fsproj
            //Easy way to detect new fsproj is to check the msbuild version of .fsproj
            //  MSBuild version 15 (`ToolsVersion="15.0"`) is the new project format
            //Post preview5 has (`Sdk="Fsharp.NET.Sdk;Microsoft.NET.Sdk"`), use that
            //  for checking .NET Core fsproj
            //The `dotnet-compile-fsc.rsp` are created also in `preview3+`, so we can
            //  reuse the same behaviour of `preview2`
            let rec getProjectType (sr:StreamReader) limit =
                // only preview3-5 uses ToolsVersion='15.0'
                // post preview5 dropped this, check Sdk field
                let isNetCore (line:string) = line.Contains("=\"15.0\"") || line.Contains("Fsharp.NET.Sdk")
                if limit = 0 then
                    Unsupported // unsupported project type
                else
                    let line = sr.ReadLine()
                    if not <| line.Contains("ToolsVersion") && not <| line.Contains("Sdk=") then
                        getProjectType sr (limit-1)
                    else // both net45 and preview3-5 have 'ToolsVersion', > 5 has 'Sdk'
                        if isNetCore line then NetCore else Net45
            if not <| File.Exists(projectFileName) then Net45 // no such file is handled downstream
            elif Path.GetExtension file = ".json" then NetCore // dotnet core preview 2 or earlier<commit_msg>Allow for inconsistent casing of "Fsharp" when detecting MSBuild 15<commit_after>namespace FsAutoComplete

open System
open System.IO
open Microsoft.FSharp.Compiler.SourceCodeServices
open FSharpLint.Application
open FsAutoComplete.UnopenedNamespacesResolver
open FsAutoComplete.UnionPatternMatchCaseGenerator
open Microsoft.FSharp.Compiler


module Response = CommandResponse

type Commands (serialize : Serializer) =
    let checker = FSharpCompilerServiceChecker()
    let state = FsAutoComplete.State.Initial
    let fsharpLintConfig = ConfigurationManager.ConfigurationManager()

    member private __.SerializeResultAsync (successToString: Serializer -> 'a -> Async<string>, ?failureToString: Serializer -> string -> string) =
        Async.bind <| function
            // A failure is only info here, as this command is expected to be
            // used 'on idle', and frequent errors are expected.
            | Result.Failure e -> async.Return [(defaultArg failureToString Response.info) serialize e]
            | Result.Success r -> successToString serialize r |> Async.map List.singleton

    member private x.SerializeResult (successToString: Serializer -> 'a -> string, ?failureToString: Serializer -> string -> string) =
        x.SerializeResultAsync ((fun s x -> successToString s x |> async.Return), ?failureToString = failureToString)

    member __.TryGetRecentTypeCheckResultsForFile = checker.TryGetRecentCheckResultsForFile
    member __.TryGetFileCheckerOptionsWithLinesAndLineStr = state.TryGetFileCheckerOptionsWithLinesAndLineStr
    member __.TryGetFileCheckerOptionsWithLines = state.TryGetFileCheckerOptionsWithLines
    member __.Files = state.Files

    member __.Parse file lines version = async {
        let colorizations = state.ColorizationOutput
        let parse' fileName text options =
            async {
                let! result = checker.ParseAndCheckFileInProject(fileName, version, text, options)
                return
                    match result with
                    | Failure e -> [Response.error serialize e]
                    | Success (parseResult, checkResults) ->
                        match checkResults with
                        | FSharpCheckFileAnswer.Aborted -> [Response.info serialize "Parse aborted"]
                        | FSharpCheckFileAnswer.Succeeded results ->
                            let errors = Array.append results.Errors parseResult.Errors
                            if colorizations then
                                [ Response.errors serialize (errors, fileName)
                                  Response.colorizations serialize (results.GetExtraColorizationsAlternate()) ]
                            else [ Response.errors serialize (errors, fileName) ]
            }
        let file = Path.GetFullPath file
        let text = String.concat "\n" lines

        if Utils.isAScript file then
            let! checkOptions = checker.GetProjectOptionsFromScript(file, text)
            state.AddFileTextAndCheckerOptions(file, lines, checkOptions)
            return! parse' file text checkOptions
        else
            let checkOptions = state.GetCheckerOptions(file, lines)
            return! parse' file text checkOptions
    }

    member __.ParseAndCheckProjectsInBackgroundForFile file = async {
        do checker.CheckProjectsInBackgroundForFile (file, state.FileCheckOptions.ToSeq() )
        return [Response.errors serialize ([||], "") ]
    }

    member __.ParseProjectsForFile file = async {
        let! res = checker.ParseProjectsForFile(file, state.FileCheckOptions.ToSeq())
        return
            match res with
            | Failure e -> [Response.error serialize e]
            | Success results ->
                let errors = results |> Array.collect (fun r -> r.Errors)
                [ Response.errors serialize (errors, file)]
    }

    // member __.FileChecked =
    //     checker.FileChecked
    //     |> Event.map (fun fn ->
    //         let file = Path.GetFullPath fn
    //         let res = state.FileCheckOptions |> Seq.tryFind (fun kv -> Path.GetFullPath kv.Key = file)
    //         match res with
    //         | None  -> async { return [Response.info serialize ( sprintf "Project for file not found: %s" file) ]  }
    //         | Some kv ->
    //             async {
    //                 let result= checker.TryGetRecentCheckResultsForFile(fn, kv.Value)
    //                 return
    //                     match result with
    //                     | None -> [Response.info serialize "File not parsed"]
    //                     | Some res -> [ Response.errors serialize (res.GetCheckResults.Errors, file) ]
    //             })

    member __.Project projectFileName verbose onChange = async {
        let projectFileName = Path.GetFullPath projectFileName
        let project = state.Projects.TryFind projectFileName

        let project = project |> Option.getOrElseFun (fun _ ->
            let project = new Project(projectFileName, onChange)
            state.Projects.[projectFileName] <- project
            project)

        let (|NetCore|Net45|Unsupported|) file =
            //.NET Core Sdk preview3+ replace project.json with fsproj
            //Easy way to detect new fsproj is to check the msbuild version of .fsproj
            //  MSBuild version 15 (`ToolsVersion="15.0"`) is the new project format
            //Post preview5 has (`Sdk="FSharp.NET.Sdk;Microsoft.NET.Sdk"`), use that
            //  for checking .NET Core fsproj. NB: casing of FSharp may be inconsistent.
            //The `dotnet-compile-fsc.rsp` are created also in `preview3+`, so we can
            //  reuse the same behaviour of `preview2`
            let rec getProjectType (sr:StreamReader) limit =
                // only preview3-5 uses ToolsVersion='15.0'
                // post preview5 dropped this, check Sdk field
                let isNetCore (line:string) = line.Contains("=\"15.0\"") || line.ToLower().Contains("fsharp.net.sdk")
                if limit = 0 then
                    Unsupported // unsupported project type
                else
                    let line = sr.ReadLine()
                    if not <| line.Contains("ToolsVersion") && not <| line.Contains("Sdk=") then
                        getProjectType sr (limit-1)
                    else // both net45 and preview3-5 have 'ToolsVersion', > 5 has 'Sdk'
                        if isNetCore line then NetCore else Net45
            if not <| File.Exists(projectFileName) then Net45 // no such file is handled downstream
            elif Path.GetExtension file = ".json" then NetCore // dotnet core preview 2 or earlier
{'fraction_non_alphanumeric': 0.08269710484541537, 'fraction_numerical': 0.004989039231990324, 'mean_word_length': 2.0738847583643123, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3205597', 'n_tokens_mistral': 3369, 'n_tokens_neox': 3224, 'n_words': 977}