├── .config └── dotnet-tools.json ├── .editorconfig ├── .fantomasignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── main.yml │ └── pr.yml ├── .gitignore ├── .gitpod.yml ├── Directory.Build.props ├── LICENSE ├── README.md ├── build.fsx ├── fantomas-tools.sln ├── global.json ├── infrastructure ├── .gitignore ├── Program.fs ├── Pulumi.main.yaml ├── Pulumi.yaml ├── infrastructure.fsproj ├── packages.lock.json └── scratch.ps1 └── src ├── client ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── bun.lockb ├── fsharp │ ├── ASTViewer │ │ ├── Decoders.fs │ │ ├── Encoders.fs │ │ ├── Model.fs │ │ ├── State.fs │ │ ├── State.fsi │ │ ├── View.fs │ │ └── View.fsi │ ├── App.fs │ ├── BubbleMessage.fs │ ├── Editor.fs │ ├── FantomasOnline │ │ ├── Decoders.fs │ │ ├── Encoders.fs │ │ ├── Model.fs │ │ ├── State.fs │ │ ├── View.fs │ │ └── View.fsi │ ├── FantomasTools.fsproj │ ├── Http.fs │ ├── Loader.fs │ ├── Model.fs │ ├── Navigation.fs │ ├── Oak │ │ ├── Decoders.fs │ │ ├── Encoders.fs │ │ ├── Graph.fs │ │ ├── GraphView.fs │ │ ├── Model.fs │ │ ├── State.fs │ │ ├── View.fs │ │ └── View.fsi │ ├── SettingControls.fs │ ├── State.fs │ ├── Style.fs │ ├── UrlTools.fs │ ├── Utils.fs │ ├── VersionBar.fs │ ├── View.fs │ └── packages.lock.json ├── index.html ├── package.json ├── public │ ├── debug.html │ ├── fantomas_logo.png │ ├── logo.png │ └── robots.txt ├── src │ ├── index.jsx │ └── styles │ │ └── style.css └── vite.config.js ├── server ├── ASTViewer │ ├── ASTViewer.fsproj │ ├── Decoders.fs │ ├── Encoders.fs │ ├── ExpandedAST.fs │ ├── ExpandedAST.fsi │ ├── GetAST.fs │ ├── Lambda.fs │ ├── Program.fs │ └── packages.lock.json ├── AWSLambdaExtensions.fs ├── FantomasOnline.Shared │ ├── Decoders.fs │ ├── Encoders.fs │ └── Http.fs ├── FantomasOnlineMain │ ├── .gitignore │ ├── FantomasOnlineMain.fsproj │ ├── FormatCode.fs │ ├── Lambda.fs │ ├── Program.fs │ ├── host.json │ └── packages.lock.json ├── FantomasOnlinePreview │ ├── .gitignore │ ├── FantomasOnlinePreview.fsproj │ ├── FormatCode.fs │ ├── Lambda.fs │ ├── Program.fs │ ├── host.json │ └── packages.lock.json ├── FantomasOnlineV5 │ ├── .gitignore │ ├── FantomasOnlineV5.fsproj │ ├── FormatCode.fs │ ├── Lambda.fs │ ├── Program.fs │ ├── host.json │ └── packages.lock.json ├── FantomasOnlineV6 │ ├── .gitignore │ ├── FantomasOnlineV6.fsproj │ ├── FormatCode.fs │ ├── Lambda.fs │ ├── Program.fs │ ├── host.json │ └── packages.lock.json ├── FantomasOnlineV7 │ ├── .gitignore │ ├── FantomasOnlineV7.fsproj │ ├── FormatCode.fs │ ├── Lambda.fs │ ├── Program.fs │ ├── host.json │ └── packages.lock.json ├── HttpConstants.fs ├── OakViewer │ ├── .gitignore │ ├── Decoders.fs │ ├── Encoders.fs │ ├── Encoders.fsi │ ├── GetOak.fs │ ├── Lambda.fs │ ├── OakViewer.fsproj │ ├── Program.fs │ ├── host.json │ └── packages.lock.json └── SuaveExtensions.fs └── shared ├── ASTViewerShared.fs ├── FantomasOnlineShared.fs ├── OakShared.fs └── Types.fs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fantomas": { 6 | "version": "7.0.0", 7 | "commands": [ 8 | "fantomas" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [src/**/*.fs] 2 | fsharp_max_if_then_else_short_width = 50 3 | fsharp_max_function_binding_width = 50 4 | fsharp_align_function_signature_to_indentation = true 5 | fsharp_keep_max_number_of_blank_lines = 1 6 | fsharp_experimental_keep_indent_in_branch = true 7 | 8 | [build.fsx] 9 | fsharp_keep_max_number_of_blank_lines = 1 10 | fsharp_blank_lines_around_nested_multiline_expressions=false 11 | 12 | [src/server/**/Program.fs] 13 | fsharp_max_infix_operator_expression = 120 14 | 15 | [{src/client/fsharp/App.fs,src/client/fsharp/SettingControls.fs,src/client/fsharp/Loader.fs,src/client/fsharp/Oak/GraphView.fs,src/client/fsharp/View.fs,src/client/fsharp/*/View.fs}] 16 | fsharp_experimental_elmish = true 17 | -------------------------------------------------------------------------------- /.fantomasignore: -------------------------------------------------------------------------------- 1 | .deps/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nojaf -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/src/client" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | allow: 10 | - dependency-name: "Fantomas.Core" 11 | - package-ecosystem: nuget 12 | directory: "/src/server/FantomasOnlineV7" 13 | schedule: 14 | interval: daily 15 | time: "10:00" 16 | open-pull-requests-limit: 10 17 | allow: 18 | - dependency-name: "Fantomas.Core" -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ main ] 10 | repository_dispatch: 11 | types: fantomas-commit-on-main 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v4 24 | - uses: oven-sh/setup-bun@v1 25 | 26 | - name: Setup dotnet 27 | uses: actions/setup-dotnet@v4 28 | 29 | - name: Download Fantomas 30 | run: dotnet fsi build.fsx -p Fantomas-Git 31 | 32 | - name: Run CI Target 33 | run: dotnet fsi build.fsx 34 | 35 | - name: Deploy Frontend 36 | uses: peaceiris/actions-gh-pages@v3 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | publish_dir: ./artifacts/client 40 | 41 | - name: Configure AWS Credentials 42 | uses: aws-actions/configure-aws-credentials@v1 43 | with: 44 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 45 | aws-region: ${{ secrets.AWS_REGION }} 46 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 47 | 48 | - uses: pulumi/actions@v3 49 | with: 50 | command: up 51 | stack-name: main 52 | work-dir: ./infrastructure 53 | env: 54 | PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | pull_request: 7 | branches: [ main ] 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | # This workflow contains a single job called "build" 12 | build: 13 | # The type of runner that the job will run on 14 | runs-on: ubuntu-latest 15 | 16 | # Steps represent a sequence of tasks that will be executed as part of the job 17 | steps: 18 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 19 | - uses: actions/checkout@v4 20 | - uses: oven-sh/setup-bun@v1 21 | 22 | - name: Setup dotnet 23 | uses: actions/setup-dotnet@v4 24 | 25 | - name: Clone Fantomas 26 | run: dotnet fsi build.fsx -p Fantomas-Git 27 | 28 | - name: Build 29 | run: dotnet fsi build.fsx -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-dotnet 2 | vscode: 3 | extensions: 4 | - muhammad-sammy.csharp 5 | - ionide.ionide-fsharp 6 | - syler.sass-indented 7 | tasks: 8 | - name: Download Fantomas dependency 9 | - command: rm paket-files/ -rf && dotnet tool restore && dotnet fake run build.fsx -t Install -p 2 10 | ports: 11 | - port: 9060 12 | onOpen: open-browser 13 | visibility: public 14 | - port: 7899 15 | onOpen: ignore 16 | visibility: public 17 | - port: 7412 18 | onOpen: ignore 19 | visibility: public 20 | - port: 9856 21 | onOpen: ignore 22 | visibility: public 23 | - port: 11084 24 | onOpen: ignore 25 | visibility: public 26 | - port: 2568 27 | onOpen: ignore 28 | visibility: public 29 | - port: 9007 30 | onOpen: ignore 31 | visibility: public 32 | - port: 10707 33 | onOpen: ignore 34 | visibility: public 35 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | FS0025 6 | 3390;$(WarnOn) 7 | true 8 | 9 | true 10 | NU1603 11 | true 12 | true 13 | preview 14 | $(OtherFlags) --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen 15 | 16 | $(MSBuildThisFileDirectory).deps\fantomas 17 | $(MSBuildThisFileDirectory).deps\v7.0 18 | LatestMajor 19 | true 20 | en 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Florian Verdonck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fantomas tools 2 | 3 | Collection of tools used when developing for Fantomas 4 | 5 | ## Prerequisites 6 | 7 | To run this tool locally you need: 8 | 9 | * [Bun](https://bun.sh/) 10 | * [.NET 8.x SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) 11 | 12 | ## Running locally 13 | 14 | * Pull in the source dependencies: 15 | 16 | ```shell 17 | dotnet fsi build.fsx -- -p Fantomas-Git 18 | ``` 19 | 20 | * Run the Watch pipeline: 21 | 22 | ```shell 23 | dotnet fsi build.fsx -- -p Watch 24 | ``` 25 | 26 | Making changes should reflect in the tool. 27 | 28 | Or try the Run pipeline: 29 | 30 | ```shell 31 | dotnet fsi build -- -p Start 32 | ``` 33 | 34 | This will run a published version of the tools. 35 | 36 | * Open http://localhost:9060 37 | 38 | ## Running in Gitpod 39 | 40 | * Open the repository via https://gitpod.io/#https://github.com/fsprojects/fantomas-tools 41 | 42 | * Run 43 | 44 | ```shell 45 | dotnet fsi build.fsx -- -p Fantomas-Git 46 | ``` 47 | 48 | ```shell 49 | dotnet fsi build.fsx -- -p Watch 50 | ``` 51 | 52 | * Open browser for port `9060` 53 | 54 | ## Other pipelines 55 | 56 | To see any other avaiable build script pipelines: 57 | 58 | ```shell 59 | dotnet fsi build.fsx -- --help 60 | ``` -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.400", 4 | "rollForward": "latestPatch" 5 | } 6 | } -------------------------------------------------------------------------------- /infrastructure/Pulumi.main.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | aws:region: eu-west-1 3 | pulumi:template: aws-fsharp 4 | -------------------------------------------------------------------------------- /infrastructure/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: fantomas-aws 2 | runtime: dotnet 3 | description: A minimal AWS F# Pulumi program -------------------------------------------------------------------------------- /infrastructure/infrastructure.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /infrastructure/scratch.ps1: -------------------------------------------------------------------------------- 1 | az storage cors add --methods GET OPTIONS --service t --origins "https://fsprojects.github.io" "http://localhost:63342" --account-name "storfantomasmain" --max-age 3600 --exposed-headers "*" --allowed-headers "*" -------------------------------------------------------------------------------- /src/client/.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | build 3 | web_modules 4 | node_modules 5 | snowpack.key 6 | snowpack.crt 7 | .snowpack -------------------------------------------------------------------------------- /src/client/.prettierignore: -------------------------------------------------------------------------------- 1 | src/bin -------------------------------------------------------------------------------- /src/client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /src/client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fred K. Schott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/client/README.md: -------------------------------------------------------------------------------- 1 | # New Project 2 | 3 | > ✨ Bootstrapped with Create Snowpack App (CSA). 4 | 5 | ## Available Scripts 6 | 7 | ### npm start 8 | 9 | Runs the app in the development mode. 10 | Open http://localhost:8080 to view it in the browser. 11 | 12 | The page will reload if you make edits. 13 | You will also see any lint errors in the console. 14 | 15 | ### npm run build 16 | 17 | Builds a static copy of your site to the `build/` folder. 18 | Your app is ready to be deployed! 19 | 20 | **For the best production performance:** Add a build bundler plugin like "@snowpack/plugin-webpack" to your `snowpack.config.js` config file. 21 | 22 | ### npm test 23 | 24 | Launches the application test runner. 25 | Run with the `--watch` flag (`npm test -- --watch`) to run in interactive watch mode. 26 | -------------------------------------------------------------------------------- /src/client/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/fantomas-tools/a3382db67bcca1bf88988ba4e76741e845be7f99/src/client/bun.lockb -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/Decoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.Decoders 2 | 3 | open ASTViewer.Shared 4 | open Elmish 5 | open FantomasTools.Client 6 | open FantomasTools.Client.ASTViewer.Model 7 | open Thoth.Json 8 | 9 | let decodeUrlModel: Decoder list> = 10 | Decode.object (fun get -> 11 | // Optional to make old urls still work. 12 | let expand = get.Optional.Field "expand" Decode.bool 13 | 14 | [ match expand with 15 | | Some expand -> yield Cmd.ofMsg (SetExpand expand) 16 | | None -> () ]) 17 | 18 | let decodeKeyValue: Decoder = fun _ -> Ok 19 | 20 | #nowarn "40" 21 | 22 | let responseDecoder: Decoder = 23 | Decode.object (fun get -> 24 | { Ast = get.Required.Field "ast" Decode.string 25 | Diagnostics = get.Required.Field "diagnostics" (Decode.array Diagnostic.Decode) }) 26 | 27 | let decodeResult json = Decode.fromString responseDecoder json 28 | -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/Encoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.Encoders 2 | 3 | open ASTViewer.Shared 4 | open FantomasTools.Client 5 | open Thoth.Json 6 | 7 | let encodeUrlModel expand (bubble: BubbleModel) : JsonValue = 8 | Encode.object 9 | [ "defines", Encode.string bubble.Defines 10 | "isFsi", Encode.bool bubble.IsFsi 11 | "code", Encode.string bubble.SourceCode 12 | "expand", Encode.bool expand ] 13 | 14 | let encodeInput (input: Request) = 15 | Encode.object 16 | [ "sourceCode", Encode.string input.SourceCode 17 | "defines", (Array.map Encode.string input.Defines |> Encode.array) 18 | "isFsi", Encode.bool input.IsFsi 19 | "expand", Encode.bool input.Expand ] 20 | |> Encode.toString 2 21 | -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/Model.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.Model 2 | 3 | open FantomasTools.Client 4 | 5 | type Msg = 6 | | Bubble of BubbleMessage 7 | | VersionFound of string 8 | | DoParse 9 | | ASTParsed of ASTViewer.Shared.Response 10 | | Error of string 11 | | SetExpand of value: bool 12 | 13 | [] 14 | type AstViewerTabState = 15 | | Loading 16 | | Result of ASTViewer.Shared.Response 17 | | Error of string 18 | 19 | type Model = 20 | { State: AstViewerTabState 21 | Version: string 22 | Expand: bool } 23 | -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/State.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.State 2 | 3 | open System 4 | open Elmish 5 | open Thoth.Json 6 | open Fable.Core 7 | open ASTViewer 8 | open FantomasTools.Client.ASTViewer.Model 9 | open FantomasTools.Client 10 | open FantomasTools.Client.ASTViewer.Decoders 11 | open FantomasTools.Client.ASTViewer.Encoders 12 | 13 | [] 14 | let backend: string = jsNative 15 | 16 | let getVersion () = sprintf "%s/%s" backend "version" |> Http.getText 17 | 18 | let fetchNodeRequest url (payload: Shared.Request) dispatch = 19 | let json = encodeInput payload 20 | 21 | Http.postJson url json 22 | |> Promise.iter (fun (status, body) -> 23 | match status with 24 | | 200 -> 25 | match decodeResult body with 26 | | Ok r -> ASTParsed r 27 | | Result.Error err -> Error $"failed to decode response: %A{err}" 28 | | 400 -> Error body 29 | | 413 -> Error "the input was too large to process" 30 | | _ -> Error body 31 | |> dispatch) 32 | 33 | let fetchUntypedAST (payload: Shared.Request) dispatch = 34 | let url = $"%s{backend}/untyped-ast" 35 | fetchNodeRequest url payload dispatch 36 | 37 | let initialModel = 38 | { State = AstViewerTabState.Result { Ast = ""; Diagnostics = Array.empty } 39 | Version = "" 40 | Expand = true } 41 | 42 | let getMessageFromError (ex: exn) = Error ex.Message 43 | 44 | // defines the initial state and initial command (= side-effect) of the application 45 | let init isActive : Model * Cmd = 46 | let cmd = 47 | Cmd.batch 48 | [ if isActive then 49 | yield! UrlTools.restoreModelFromUrl decodeUrlModel [] 50 | yield Cmd.OfPromise.either getVersion () VersionFound getMessageFromError ] 51 | 52 | initialModel, cmd 53 | 54 | let getDefines (bubble: BubbleModel) = 55 | bubble.Defines.Split([| ' '; ','; ';' |], StringSplitOptions.RemoveEmptyEntries) 56 | 57 | let modelToParseRequest expand (bubble: BubbleModel) : Shared.Request = 58 | { SourceCode = bubble.SourceCode 59 | Defines = getDefines bubble 60 | IsFsi = bubble.IsFsi 61 | Expand = expand } 62 | 63 | let updateUrl expand (bubble: BubbleModel) _ = 64 | let json = Encode.toString 2 (encodeUrlModel expand bubble) 65 | UrlTools.updateUrlWithData json 66 | 67 | // The update function computes the next state of the application based on the current state and the incoming events/messages 68 | // It can also run side-effects (encoded as commands) like calling the server via Http. 69 | // these commands in turn, can dispatch messages to which the update function will react. 70 | let update (bubble: BubbleModel) (msg: Msg) (model: Model) : Model * Cmd = 71 | match msg with 72 | | Msg.Bubble _ -> model, Cmd.none // handle in upper update function 73 | | ASTParsed astResult -> 74 | let nextModel = 75 | { model with 76 | State = AstViewerTabState.Result astResult } 77 | 78 | let resultCmd = Cmd.ofMsg (BubbleMessage.SetResultCode astResult.Ast |> Msg.Bubble) 79 | 80 | let diagnosticsCmd = 81 | Cmd.ofMsg (BubbleMessage.SetDiagnostics astResult.Diagnostics |> Msg.Bubble) 82 | 83 | nextModel, Cmd.batch [ resultCmd; diagnosticsCmd ] 84 | | Error e -> 85 | let nextModel = 86 | { model with 87 | State = AstViewerTabState.Error e } 88 | 89 | nextModel, Cmd.none 90 | | DoParse -> 91 | let parseRequest = modelToParseRequest model.Expand bubble 92 | 93 | let cmd = 94 | Cmd.batch 95 | [ Cmd.ofEffect (fetchUntypedAST parseRequest) 96 | Cmd.ofEffect (updateUrl model.Expand bubble) ] 97 | 98 | { model with 99 | State = AstViewerTabState.Loading }, 100 | cmd 101 | 102 | | VersionFound version -> { model with Version = version }, Cmd.none 103 | | SetExpand value -> { model with Expand = value }, Cmd.none 104 | -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/State.fsi: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.State 2 | 3 | open Elmish 4 | open FantomasTools.Client.ASTViewer.Model 5 | open FantomasTools.Client 6 | 7 | val init: isActive: bool -> Model * Cmd 8 | val update: bubble: BubbleModel -> msg: Msg -> model: Model -> Model * Cmd 9 | -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/View.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.View 2 | 3 | open System 4 | open System.Text.RegularExpressions 5 | open Fable.Core 6 | open Fable.Core.JsInterop 7 | open Fable.React 8 | open Fable.React.Props 9 | open FantomasTools.Client 10 | open FantomasTools.Client.ASTViewer.Model 11 | 12 | // Enter 'localStorage.setItem("debugASTRangeHighlight", "true");' in your browser console to enable. 13 | let debugASTRangeHighlight: bool = 14 | not (String.IsNullOrWhiteSpace(Browser.WebStorage.localStorage.getItem "debugASTRangeHighlight")) 15 | 16 | let cursorChanged (bubbleMsg: BubbleMessage -> unit) (model: Model) (e: obj) : unit = 17 | let lineNumber: int = e?position?lineNumber 18 | let column: int = e?position?column 19 | 20 | match model.State with 21 | | AstViewerTabState.Result { Ast = astText } -> 22 | let lines = astText.Split([| "\r\n"; "\n" |], StringSplitOptions.None) 23 | // Try and get the line where the cursor clicked in the AST editor 24 | match Array.tryItem (lineNumber - 1) lines with 25 | | None -> () 26 | | Some sourceLine -> 27 | 28 | if debugASTRangeHighlight then 29 | JS.console.log (sourceLine.Trim()) 30 | 31 | let pattern = @"\(\d+,\d+--\d+,\d+\)" 32 | 33 | let rangeDigits = 34 | Regex.Matches(sourceLine, pattern) 35 | |> Seq.cast 36 | |> fun matches -> 37 | if debugASTRangeHighlight then 38 | JS.console.log matches 39 | 40 | matches 41 | |> Seq.tryPick (fun m -> 42 | if debugASTRangeHighlight then 43 | JS.console.log m.Value 44 | 45 | let startIndex = m.Index 46 | let endIndex = m.Index + m.Value.Length 47 | // Verify the match contains the cursor column. 48 | if startIndex <= column && column <= endIndex then 49 | m.Value.Split([| ','; '-'; '('; ')' |], StringSplitOptions.RemoveEmptyEntries) 50 | |> Array.map int 51 | |> Array.toList 52 | |> Some 53 | else 54 | None) 55 | 56 | match rangeDigits with 57 | | Some [ startLine; startColumn; endLine; endColumn ] -> 58 | let range = 59 | { StartLine = startLine 60 | StartColumn = startColumn 61 | EndLine = endLine 62 | EndColumn = endColumn } 63 | 64 | bubbleMsg (BubbleMessage.HighLight range) 65 | | _ -> bubbleMsg (BubbleMessage.HighLight Range.Zero) 66 | 67 | | _ -> () 68 | 69 | let commands dispatch = 70 | button [ ClassName Style.Primary; OnClick(fun _ -> dispatch DoParse) ] [ str "Show Untyped AST" ] 71 | 72 | let settings (bubble: BubbleModel) (model: Model) dispatch = 73 | fragment [] [ 74 | VersionBar.versionBar $"FSC - %s{model.Version}" 75 | SettingControls.input 76 | "ast-defines" 77 | (BubbleMessage.SetDefines >> Bubble >> dispatch) 78 | (str "Defines") 79 | "Enter your defines separated with a space" 80 | bubble.Defines 81 | SettingControls.toggleButton 82 | (fun _ -> BubbleMessage.SetFsi true |> Bubble |> dispatch) 83 | (fun _ -> BubbleMessage.SetFsi false |> Bubble |> dispatch) 84 | "*.fsi" 85 | "*.fs" 86 | (str "File extension") 87 | bubble.IsFsi 88 | SettingControls.toggleButton 89 | (fun _ -> dispatch (SetExpand false)) 90 | (fun _ -> dispatch (SetExpand true)) 91 | "Regular" 92 | "Expanded" 93 | (str "Mode") 94 | (not model.Expand) 95 | ] 96 | 97 | let view (model: Model) = 98 | match model.State with 99 | | AstViewerTabState.Loading -> Loader.tabLoading 100 | | _ -> null 101 | -------------------------------------------------------------------------------- /src/client/fsharp/ASTViewer/View.fsi: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.ASTViewer.View 2 | 3 | open Fable.React 4 | open FantomasTools.Client 5 | open FantomasTools.Client.ASTViewer.Model 6 | 7 | val cursorChanged: bubbleMsg: (BubbleMessage -> unit) -> model: Model -> e: obj -> unit 8 | val commands: dispatch: (Msg -> unit) -> ReactElement 9 | val settings: bubble: BubbleModel -> model: Model -> dispatch: (Msg -> unit) -> ReactElement 10 | val view: model: Model -> ReactElement 11 | -------------------------------------------------------------------------------- /src/client/fsharp/App.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.App 2 | 3 | open Fable.Core.JsInterop 4 | open Browser.Types 5 | open Fable.React 6 | open Feliz 7 | open Feliz.Router 8 | open Feliz.UseElmish 9 | open Browser.Dom 10 | open FantomasTools.Client 11 | 12 | [] 13 | let App () = 14 | let model, dispatch = React.useElmish (State.init, State.update, [||]) 15 | 16 | let onUrlChanged url = 17 | let activeTab = Navigation.parseUrl url 18 | dispatch (Model.Msg.SelectTab activeTab) 19 | 20 | let routes = View.rightPane model dispatch 21 | 22 | fragment [] [ 23 | View.navigation dispatch 24 | main [] [ 25 | View.editor model dispatch 26 | React.router [ router.onUrlChanged onUrlChanged; router.children [ routes ] ] 27 | ] 28 | ] 29 | 30 | let createRoot: Element -> {| render: ReactElement -> unit |} = 31 | import "createRoot" "react-dom/client" 32 | 33 | let root = createRoot (document.getElementById "app") 34 | root.render (React.strictMode [ App() ]) 35 | -------------------------------------------------------------------------------- /src/client/fsharp/BubbleMessage.fs: -------------------------------------------------------------------------------- 1 | namespace FantomasTools.Client 2 | 3 | /// Messages sent from the individual tab update loop to the main update loop 4 | type BubbleMessage = 5 | | SetFsi of bool 6 | | SetDefines of string 7 | | SetSourceCode of string 8 | | SetResultCode of string 9 | | SetDiagnostics of Diagnostic array 10 | | HighLight of Range 11 | 12 | /// This is the shared data among all the tabs. 13 | type BubbleModel = 14 | { 15 | SourceCode: string 16 | IsFsi: bool 17 | Defines: string 18 | /// The result of the used tool. 19 | /// Used in AST and Fantomas tab. 20 | ResultCode: string 21 | Diagnostics: Diagnostic array 22 | HighLight: Range 23 | } 24 | -------------------------------------------------------------------------------- /src/client/fsharp/FantomasOnline/Decoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.FantomasOnline.Decoders 2 | 3 | open FantomasTools.Client 4 | open Thoth.Json 5 | open FantomasOnline.Shared 6 | 7 | let private optionDecoder: Decoder = 8 | Decode.object (fun get -> 9 | let t = get.Required.Field "$type" Decode.string 10 | 11 | if t = "int" then 12 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.int) 13 | |> FantomasOption.IntOption 14 | elif t = "bool" then 15 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.bool) 16 | |> FantomasOption.BoolOption 17 | elif t = "multilineFormatterType" then 18 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.string) 19 | |> FantomasOption.MultilineFormatterTypeOption 20 | elif t = "endOfLineStyle" then 21 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.string) 22 | |> FantomasOption.EndOfLineStyleOption 23 | elif t = "multilineBracketStyle" then 24 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.string) 25 | |> FantomasOption.MultilineBracketStyleOption 26 | else 27 | failwithf $"Cannot decode %s{t}") 28 | 29 | let decodeOptions json = 30 | Decode.fromString (Decode.array optionDecoder) json 31 | |> Result.map (Array.sortBy sortByOption >> List.ofArray) 32 | 33 | let decodeOptionsFromUrl: Decoder = 34 | Decode.object (fun get -> get.Required.Field "settings" (Decode.list optionDecoder)) 35 | 36 | let decodeFormatResponse: Decoder = 37 | Decode.object (fun get -> 38 | { FirstFormat = get.Required.Field "firstFormat" Decode.string 39 | FirstValidation = get.Required.Field "firstValidation" (Decode.array Diagnostic.Decode) 40 | SecondFormat = get.Optional.Field "secondFormat" Decode.string 41 | SecondValidation = get.Required.Field "secondValidation" (Decode.array Diagnostic.Decode) }) 42 | -------------------------------------------------------------------------------- /src/client/fsharp/FantomasOnline/Encoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.FantomasOnline.Encoders 2 | 3 | open FantomasOnline.Shared 4 | open FantomasTools.Client.FantomasOnline.Model 5 | open Thoth.Json 6 | 7 | let private encodeOption fantomasOption = 8 | let key, value = 9 | match fantomasOption with 10 | | IntOption(o, k, v) -> "int", Encode.tuple3 Encode.int Encode.string Encode.int (o, k, v) 11 | | BoolOption(o, k, v) -> "bool", Encode.tuple3 Encode.int Encode.string Encode.bool (o, k, v) 12 | | MultilineFormatterTypeOption(o, k, v) -> 13 | "multilineFormatterType", Encode.tuple3 Encode.int Encode.string Encode.string (o, k, v) 14 | | EndOfLineStyleOption(o, k, v) -> 15 | "endOfLineStyle", Encode.tuple3 Encode.int Encode.string Encode.string (o, k, v) 16 | | MultilineBracketStyleOption(o, k, v) -> 17 | "multilineBracketStyle", Encode.tuple3 Encode.int Encode.string Encode.string (o, k, v) 18 | 19 | Encode.object [ "$type", Encode.string key; "$value", value ] 20 | 21 | let private encodeUserSettings model = 22 | model.UserOptions 23 | |> Map.toList 24 | |> List.sortBy (snd >> sortByOption) 25 | |> List.map (snd >> encodeOption) 26 | |> Encode.list 27 | 28 | let encodeRequest code isFsi (model: Model) = 29 | Encode.object 30 | [ "sourceCode", Encode.string code 31 | "options", encodeUserSettings model 32 | "isFsi", Encode.bool isFsi ] 33 | |> Encode.toString 2 34 | 35 | let encodeUrlModel code isFsi model = 36 | Encode.object 37 | [ "code", Encode.string code 38 | "settings", encodeUserSettings model 39 | "isFsi", Encode.bool isFsi ] 40 | 41 | let encodeUserSettingToConfiguration options = 42 | let encodeValue option = 43 | match option with 44 | | IntOption(_, _, v) -> Encode.int v 45 | | BoolOption(_, _, v) -> Encode.bool v 46 | | MultilineFormatterTypeOption(_, _, v) 47 | | EndOfLineStyleOption(_, _, v) 48 | | MultilineBracketStyleOption(_, _, v) -> Encode.string v 49 | 50 | options 51 | |> List.map (fun option -> getOptionKey option, encodeValue option) 52 | |> Encode.object 53 | |> Encode.toString 4 54 | -------------------------------------------------------------------------------- /src/client/fsharp/FantomasOnline/Model.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.FantomasOnline.Model 2 | 3 | open FantomasOnline.Shared 4 | open FantomasTools.Client 5 | 6 | type FantomasMode = 7 | | V5 8 | | V6 9 | | V7 10 | | Main // main branch 11 | | Preview // also main branch 12 | 13 | type Msg = 14 | | Bubble of BubbleMessage 15 | | VersionReceived of string 16 | | OptionsReceived of FantomasOption list 17 | | FormatException of string 18 | | Format 19 | | FormattedReceived of FormatResponse 20 | | UpdateOption of (string * FantomasOption) 21 | | ChangeMode of FantomasMode 22 | | CopySettings 23 | | UpdateSettingsFilter of string 24 | | ResetSettings 25 | 26 | [] 27 | type FantomasTabState = 28 | | LoadingOptions 29 | | OptionsLoaded 30 | | LoadingFormatRequest 31 | | FormatResult of FormatResponse 32 | | FormatError of string 33 | 34 | type Model = 35 | { Version: string 36 | DefaultOptions: FantomasOption list 37 | UserOptions: Map 38 | Mode: FantomasMode 39 | State: FantomasTabState 40 | SettingsFilter: string } 41 | 42 | member this.SettingsChangedByTheUser = 43 | let defaultValues = this.DefaultOptions |> List.sortBy sortByOption 44 | 45 | let userValues = 46 | this.UserOptions |> Map.toList |> List.map snd |> List.sortBy sortByOption 47 | 48 | List.zip defaultValues userValues 49 | |> List.filter (fun (dv, uv) -> dv <> uv) 50 | |> List.map snd 51 | 52 | member this.MaxLineLength: int = 53 | tryGetOptionValue this.UserOptions this.DefaultOptions "MaxLineLength" int 54 | |> Option.defaultValue 120 55 | -------------------------------------------------------------------------------- /src/client/fsharp/FantomasOnline/View.fsi: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.FantomasOnline.View 2 | 3 | open Fable.React 4 | open FantomasTools.Client 5 | open FantomasTools.Client.FantomasOnline.Model 6 | 7 | val commands: bubble: BubbleModel -> model: Model -> dispatch: (Msg -> unit) -> ReactElement 8 | val settings: isFsi: bool -> model: Model -> dispatch: (Msg -> unit) -> ReactElement 9 | val view: model: Model -> dispatch: (Msg -> unit) -> ReactElement 10 | -------------------------------------------------------------------------------- /src/client/fsharp/FantomasTools.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | FantomasTools.Client 5 | FantomasTools.Client 6 | $(DefineConstants);FABLE_COMPILER 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/client/fsharp/Http.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.Http 2 | 3 | open Fable.Core 4 | open Fable.Core.JsInterop 5 | open Fetch 6 | 7 | let postJson<'TResponse> (url: string) (body: string) : JS.Promise = 8 | let options = 9 | requestProps 10 | [ requestHeaders [ ContentType "application/json" ] 11 | Method HttpMethod.POST 12 | Body !^body ] 13 | 14 | GlobalFetch.fetch (RequestInfo.Url url, options) 15 | |> Promise.bind (fun res -> 16 | promise { 17 | let! text = res.text () 18 | return (res.Status, text) 19 | }) 20 | 21 | let getText (url: string) : JS.Promise = 22 | let options = 23 | requestProps [ requestHeaders [ ContentType "application/json" ]; Method HttpMethod.GET ] 24 | 25 | GlobalFetch.fetch (RequestInfo.Url url, options) 26 | |> Promise.bind (fun res -> res.text ()) 27 | -------------------------------------------------------------------------------- /src/client/fsharp/Loader.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.Loader 2 | 3 | open Fable.React 4 | open Fable.React.Props 5 | open FantomasTools.Client 6 | 7 | let loading = div [ Id "loading" ] [ div [] [] ] 8 | 9 | let tabLoading = div [ ClassName Style.TabContent ] [ loading ] 10 | -------------------------------------------------------------------------------- /src/client/fsharp/Model.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.Model 2 | 3 | open FantomasTools.Client 4 | 5 | type ActiveTab = 6 | | HomeTab 7 | | ASTTab 8 | | OakTab 9 | | FantomasTab of FantomasTools.Client.FantomasOnline.Model.FantomasMode 10 | 11 | type Model = 12 | { ActiveTab: ActiveTab 13 | SettingsOpen: bool 14 | Bubble: BubbleModel 15 | OakModel: OakViewer.Model.Model 16 | ASTModel: ASTViewer.Model.Model 17 | FantomasModel: FantomasOnline.Model.Model } 18 | 19 | type Msg = 20 | | SelectTab of ActiveTab 21 | | UpdateSourceCode of string 22 | | OakMsg of OakViewer.Model.Msg 23 | | ASTMsg of ASTViewer.Model.Msg 24 | | FantomasMsg of FantomasOnline.Model.Msg 25 | | ToggleSettings 26 | -------------------------------------------------------------------------------- /src/client/fsharp/Navigation.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.Navigation 2 | 3 | open FantomasTools.Client 4 | open FantomasTools.Client.Model 5 | open Elmish 6 | open Feliz.Router 7 | 8 | let cmdForCurrentTab tab (model: Model) = 9 | let noSourceCode = System.String.IsNullOrWhiteSpace model.Bubble.SourceCode 10 | 11 | match tab with 12 | | HomeTab -> Cmd.none 13 | | ASTTab -> Cmd.ofMsg ASTViewer.Model.DoParse |> Cmd.map Msg.ASTMsg 14 | | OakTab -> Cmd.ofMsg OakViewer.Model.GetOak |> Cmd.map Msg.OakMsg 15 | | FantomasTab mode -> 16 | if noSourceCode then 17 | Cmd.none 18 | elif mode <> model.FantomasModel.Mode then 19 | Cmd.batch 20 | [ Cmd.map FantomasMsg (FantomasOnline.State.getOptionsCmd mode) 21 | Cmd.map FantomasMsg (FantomasOnline.State.getVersionCmd mode) ] 22 | elif not (List.isEmpty model.FantomasModel.DefaultOptions) then 23 | Cmd.ofMsg FantomasOnline.Model.Format |> Cmd.map FantomasMsg 24 | else 25 | Cmd.none 26 | 27 | let toHash = 28 | function 29 | | HomeTab -> "#/" 30 | | OakTab -> "#/oak" 31 | | ASTTab -> "#/ast" 32 | | FantomasTab FantomasOnline.Model.V5 -> "#/fantomas/v5" 33 | | FantomasTab FantomasOnline.Model.V6 -> "#/fantomas/v6" 34 | | FantomasTab FantomasOnline.Model.V7 -> "#/fantomas/v7" 35 | | FantomasTab FantomasOnline.Model.Main -> "#/fantomas/main" 36 | | FantomasTab FantomasOnline.Model.Preview -> "#/fantomas/preview" 37 | 38 | let parseUrl segments = 39 | match segments with 40 | | [ "ast" ] 41 | | [ "ast"; Route.Query [ "data", _ ] ] -> ActiveTab.ASTTab 42 | | [ "oak" ] 43 | | [ "oak"; Route.Query [ "data", _ ] ] -> ActiveTab.OakTab 44 | | [ "fantomas"; "v5" ] 45 | | [ "fantomas"; "v5"; Route.Query [ "data", _ ] ] -> ActiveTab.FantomasTab(FantomasOnline.Model.V5) 46 | | [ "fantomas"; "v6" ] 47 | | [ "fantomas"; "v6"; Route.Query [ "data", _ ] ] -> ActiveTab.FantomasTab(FantomasOnline.Model.V6) 48 | | [ "fantomas"; "v7" ] 49 | | [ "fantomas"; "v7"; Route.Query [ "data", _ ] ] -> ActiveTab.FantomasTab(FantomasOnline.Model.V7) 50 | | [ "fantomas"; "main" ] 51 | | [ "fantomas"; "main"; Route.Query [ "data", _ ] ] -> ActiveTab.FantomasTab(FantomasOnline.Model.Main) 52 | | [ "fantomas"; "preview" ] 53 | | [ "fantomas"; "preview"; Route.Query [ "data", _ ] ] -> ActiveTab.FantomasTab(FantomasOnline.Model.Preview) 54 | | _ -> ActiveTab.HomeTab 55 | -------------------------------------------------------------------------------- /src/client/fsharp/Oak/Decoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.OakViewer.Decoders 2 | 3 | open Thoth.Json 4 | open FantomasTools.Client 5 | open FantomasTools.Client.OakViewer.Model 6 | 7 | let decodeUrlModel: Decoder = 8 | Decode.object (fun get -> get.Optional.Field "isGraphView" Decode.bool |> Option.defaultValue false) 9 | 10 | let decodeTriviaNode: Decoder = 11 | Decode.object (fun get -> 12 | { Type = get.Required.Field "type" Decode.string 13 | Range = get.Required.Field "range" Range.Decode 14 | Content = get.Optional.Field "content" Decode.string }) 15 | 16 | let rec private decodeNode (name: string) (value: JsonValue) = 17 | Decode.object 18 | (fun get -> 19 | { Type = get.Required.Field "type" Decode.string 20 | Text = get.Optional.Field "text" Decode.string 21 | Range = get.Required.Field "range" Range.Decode 22 | ContentBefore = get.Required.Field "contentBefore" (Decode.array decodeTriviaNode) 23 | Children = get.Required.Field "children" (Decode.array decodeNode) 24 | ContentAfter = get.Required.Field "contentAfter" (Decode.array decodeTriviaNode) }) 25 | name 26 | value 27 | 28 | let decodeOak: string -> obj -> Result = 29 | Decode.object (fun get -> 30 | let oak = get.Required.Field "oak" decodeNode 31 | let diagnostics = get.Required.Field "diagnostics" (Decode.array Diagnostic.Decode) 32 | oak, diagnostics) 33 | -------------------------------------------------------------------------------- /src/client/fsharp/Oak/Encoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.OakViewer.Encoders 2 | 3 | open FantomasTools.Client 4 | open Thoth.Json 5 | open FantomasTools.Client.OakViewer.Model 6 | 7 | let encodeParseRequest (pr: OakViewer.ParseRequest) = 8 | Encode.object 9 | [ "sourceCode", Encode.string pr.SourceCode 10 | "defines", Array.map Encode.string pr.Defines |> Encode.array 11 | "isFsi", Encode.bool pr.IsFsi ] 12 | |> Encode.toString 4 13 | 14 | let encodeUrlModel (bubble: BubbleModel) (model: Model) = 15 | Encode.object 16 | [ "code", Encode.string bubble.SourceCode // the "code" key is a convention 17 | "defines", Encode.string bubble.Defines 18 | "isFsi", Encode.bool bubble.IsFsi 19 | "isGraphView", Encode.bool model.IsGraphView ] 20 | -------------------------------------------------------------------------------- /src/client/fsharp/Oak/Graph.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.OakViewer.Graph 2 | 3 | open Browser.Types 4 | open Fable.Core 5 | open Fable.Core.JsInterop 6 | open Fable.React 7 | open Feliz 8 | 9 | module VisNetwork = 10 | type node = 11 | {| id: int 12 | label: string 13 | title: string 14 | level: int 15 | color: string 16 | shape: string 17 | value: int 18 | font: obj |} 19 | 20 | type edge = 21 | {| from: int 22 | ``to``: int 23 | dashes: bool |} 24 | 25 | type options = 26 | {| layout: obj 27 | interaction: obj 28 | width: string 29 | height: string 30 | nodes: obj |} 31 | 32 | [] 33 | type DataSet(_data: U2 array) = class end 34 | 35 | type data = {| nodes: DataSet; edges: DataSet |} 36 | 37 | [] 38 | type Network(_container: Element, _data: obj, _options: obj) = 39 | inherit System.Object() 40 | 41 | [] 42 | member this.OnSelect _callback : unit = jsNative 43 | 44 | [] 45 | member this.OnHover _callback : unit = jsNative 46 | 47 | [] 48 | member this.OffSelect() : unit = jsNative 49 | 50 | [] 51 | member this.OffHover() : unit = jsNative 52 | 53 | [] 54 | member this.SetData(_data: data) = jsNative 55 | 56 | [] 57 | member this.SetOptions(_options: options) = jsNative 58 | 59 | type GraphProps = 60 | {| options: VisNetwork.options 61 | data: VisNetwork.data 62 | selectNode: {| nodes: int array |} -> unit 63 | hoverNode: {| node: int |} -> unit |} 64 | 65 | type RefProp = 66 | | Ref of obj 67 | 68 | interface IHTMLProp 69 | 70 | [] 71 | let Graph (props: GraphProps) = 72 | let divRef = React.useRef null 73 | let network, setNetwork = React.useState None 74 | 75 | React.useEffectOnce (fun () -> 76 | if not (isNullOrUndefined divRef.current) then 77 | let network' = VisNetwork.Network(divRef.current, props.data, props.options) 78 | network'.OnSelect(props.selectNode) 79 | network'.OnHover(props.hoverNode) 80 | setNetwork (Some network')) 81 | 82 | React.useEffect ( 83 | fun () -> 84 | network 85 | |> Option.iter (fun network -> 86 | network.OffSelect() 87 | network.OffHover() 88 | network.SetData props.data 89 | network.OnSelect(props.selectNode) 90 | network.OnHover(props.hoverNode)) 91 | , [| box network; box props.data |] 92 | ) 93 | 94 | React.useEffect ( 95 | fun () -> network |> Option.iter (fun network -> network.SetOptions props.options) 96 | , [| box network; box props.options |] 97 | ) 98 | 99 | div [ Ref divRef ] [ str "graph, never render" ] 100 | -------------------------------------------------------------------------------- /src/client/fsharp/Oak/Model.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.OakViewer.Model 2 | 3 | open Fable.Core 4 | open FantomasTools.Client 5 | 6 | type TriviaNode = 7 | { Type: string 8 | Range: Range 9 | Content: string option } 10 | 11 | type OakNode = 12 | { Type: string 13 | Text: string option 14 | Range: Range 15 | ContentBefore: TriviaNode array 16 | Children: OakNode array 17 | ContentAfter: TriviaNode array } 18 | 19 | module GraphView = 20 | [] 21 | type NodeId = NodeId of int 22 | 23 | [] 24 | type NodeLabel = NodeLabel of string 25 | 26 | [] 27 | type NodeColor = NodeColor of string 28 | 29 | [] 30 | type NodeShape = 31 | | Ellipse 32 | | Box 33 | 34 | type Node = 35 | { Label: NodeLabel 36 | Level: int 37 | Color: NodeColor 38 | Shape: NodeShape 39 | ScaleValue: int } 40 | 41 | type Edge = 42 | { From: NodeId 43 | To: NodeId 44 | Dashed: bool } 45 | 46 | type Layout = 47 | | TopDown 48 | | LeftRight 49 | | Free 50 | 51 | type Scale = 52 | | NoScale 53 | | SubTreeNodes 54 | | AllNodes 55 | 56 | type Options = 57 | { NodeLimit: int 58 | Layout: Layout 59 | Scale: Scale 60 | ScaleMaxSize: int } 61 | 62 | type Msg = 63 | | Bubble of BubbleMessage 64 | | GetOak 65 | | OakReceived of oak: OakNode * diagnostics: Diagnostic array 66 | | FSCVersionReceived of string 67 | | SetGraphView of bool 68 | | SetGraphViewNodeLimit of int 69 | | SetGraphViewLayout of GraphView.Layout 70 | | SetGraphViewScale of GraphView.Scale 71 | | SetGraphViewScaleMax of int 72 | | GraphViewSetRoot of GraphView.NodeId 73 | | GraphViewGoBack 74 | | Error of string 75 | 76 | [] 77 | type OakViewerTabState = 78 | | Loading 79 | | Result of OakNode 80 | | Error of string 81 | 82 | type Model = 83 | { State: OakViewerTabState 84 | Version: string 85 | IsGraphView: bool 86 | GraphViewOptions: GraphView.Options 87 | GraphViewRootNodes: GraphView.NodeId list } 88 | -------------------------------------------------------------------------------- /src/client/fsharp/Oak/State.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.OakViewer.State 2 | 3 | open System 4 | open Fable.Core 5 | open FantomasTools.Client 6 | open Elmish 7 | open Thoth.Json 8 | open FantomasTools.Client.OakViewer.Model 9 | open FantomasTools.Client.OakViewer.Encoders 10 | open FantomasTools.Client.OakViewer.Decoders 11 | 12 | [] 13 | let private backend: string = jsNative 14 | 15 | let private fetchOak (payload: OakViewer.ParseRequest) dispatch = 16 | let url = sprintf "%s/get-oak" backend 17 | let json = encodeParseRequest payload 18 | 19 | Http.postJson url json 20 | |> Promise.iter (fun (status, body) -> 21 | match status with 22 | | 200 -> 23 | match Decode.fromString decodeOak body with 24 | | Ok response -> Msg.OakReceived response 25 | | Result.Error err -> Msg.Error err 26 | | _ -> Msg.Error body 27 | |> dispatch) 28 | 29 | let private fetchFSCVersion () = sprintf "%s/version" backend |> Http.getText 30 | 31 | let private initialModel: Model = 32 | { State = 33 | OakViewerTabState.Result( 34 | { Type = "Oak" 35 | Text = None 36 | Range = Range.Zero 37 | ContentBefore = Array.empty 38 | Children = Array.empty 39 | ContentAfter = Array.empty } 40 | ) 41 | Version = "???" 42 | IsGraphView = false 43 | GraphViewOptions = 44 | { Layout = GraphView.TopDown 45 | NodeLimit = 25 46 | Scale = GraphView.SubTreeNodes 47 | ScaleMaxSize = 25 } 48 | GraphViewRootNodes = [] } 49 | 50 | let private splitDefines (value: string) = 51 | value.Split([| ' '; ';' |], StringSplitOptions.RemoveEmptyEntries) 52 | 53 | let private modelToParseRequest (bubble: BubbleModel) : OakViewer.ParseRequest = 54 | { SourceCode = bubble.SourceCode 55 | Defines = splitDefines bubble.Defines 56 | IsFsi = bubble.IsFsi } 57 | 58 | let init () = 59 | let isGraphView = UrlTools.restoreModelFromUrl decodeUrlModel false 60 | 61 | let cmd = 62 | Cmd.OfPromise.either fetchFSCVersion () FSCVersionReceived (fun ex -> Error ex.Message) 63 | 64 | { initialModel with 65 | IsGraphView = isGraphView }, 66 | cmd 67 | 68 | let private updateUrl (bubble: BubbleModel) (model: Model) _ = 69 | let json = Encode.toString 2 (encodeUrlModel bubble model) 70 | UrlTools.updateUrlWithData json 71 | 72 | let update (bubble: BubbleModel) (msg: Msg) model : Model * Cmd = 73 | match msg with 74 | | Msg.Bubble _ -> model, Cmd.none // handle in upper update function 75 | | Msg.GetOak -> 76 | let parseRequest = modelToParseRequest bubble 77 | 78 | let cmd = 79 | Cmd.batch [ Cmd.ofEffect (fetchOak parseRequest); Cmd.ofEffect (updateUrl bubble model) ] 80 | 81 | { model with 82 | State = OakViewerTabState.Loading }, 83 | cmd 84 | | Msg.OakReceived(oak, diagnostics) -> 85 | let cmd = Cmd.ofMsg (Msg.Bubble(BubbleMessage.SetDiagnostics diagnostics)) 86 | 87 | { model with 88 | State = OakViewerTabState.Result oak 89 | GraphViewRootNodes = [] }, 90 | cmd 91 | | Msg.Error error -> 92 | { initialModel with 93 | State = OakViewerTabState.Error error }, 94 | Cmd.none 95 | | FSCVersionReceived version -> { model with Version = version }, Cmd.none 96 | | SetGraphView value -> let m = { model with IsGraphView = value } in m, Cmd.ofEffect (updateUrl bubble m) 97 | | SetGraphViewLayout value -> 98 | { model with 99 | GraphViewOptions = 100 | { model.GraphViewOptions with 101 | Layout = value } }, 102 | Cmd.none 103 | | SetGraphViewNodeLimit value -> 104 | { model with 105 | GraphViewOptions = 106 | { model.GraphViewOptions with 107 | NodeLimit = value } }, 108 | Cmd.none 109 | | SetGraphViewScale value -> 110 | { model with 111 | GraphViewOptions = 112 | { model.GraphViewOptions with 113 | Scale = value } }, 114 | Cmd.none 115 | | SetGraphViewScaleMax value -> 116 | { model with 117 | GraphViewOptions = 118 | { model.GraphViewOptions with 119 | ScaleMaxSize = value } }, 120 | Cmd.none 121 | | GraphViewSetRoot nodeId -> 122 | { model with 123 | GraphViewRootNodes = nodeId :: model.GraphViewRootNodes }, 124 | Cmd.none 125 | | GraphViewGoBack -> 126 | { model with 127 | GraphViewRootNodes = 128 | if model.GraphViewRootNodes = [] then 129 | [] 130 | else 131 | List.tail model.GraphViewRootNodes }, 132 | Cmd.none 133 | -------------------------------------------------------------------------------- /src/client/fsharp/Oak/View.fsi: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.OakViewer.View 2 | 3 | open Fable.React 4 | open FantomasTools.Client 5 | open FantomasTools.Client.OakViewer.Model 6 | 7 | val view: model: Model -> dispatch: (Msg -> unit) -> ReactElement 8 | val commands: dispatch: (Msg -> unit) -> ReactElement 9 | val settings: bubble: BubbleModel -> model: Model -> dispatch: (Msg -> unit) -> ReactElement 10 | -------------------------------------------------------------------------------- /src/client/fsharp/SettingControls.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.SettingControls 2 | 3 | open Fable.React 4 | open Fable.React.Props 5 | open FantomasTools.Client 6 | 7 | let input key onChange (labelValue: ReactElement) placeholder value = 8 | div [ ClassName Style.Setting ] [ 9 | label [] [ labelValue ] 10 | input [ 11 | Placeholder placeholder 12 | OnChange(fun ev -> ev.Value |> onChange) 13 | DefaultValue value 14 | Key key 15 | ] 16 | ] 17 | 18 | let private toggleButton_ onClick active label = 19 | let className = if active then Style.Active else "" 20 | 21 | button [ ClassName className; Key label; OnClick onClick ] [ str label ] 22 | 23 | let toggleButton onTrue onFalse labelTrue labelFalse (labelValue: ReactElement) value = 24 | div [ ClassName Style.Setting ] [ 25 | label [] [ labelValue ] 26 | div [ ClassName Style.ToggleButton ] [ 27 | toggleButton_ onTrue value labelTrue 28 | toggleButton_ onFalse (not value) labelFalse 29 | ] 30 | ] 31 | 32 | type MultiButtonSettings = 33 | { Label: string 34 | OnClick: obj -> unit 35 | IsActive: bool } 36 | 37 | let multiButton labelValue (options: MultiButtonSettings list) = 38 | let buttons = 39 | options 40 | |> List.map (fun { Label = l; OnClick = o; IsActive = i } -> toggleButton_ o i l) 41 | 42 | div [ ClassName Style.Setting ] [ 43 | label [] [ str labelValue ] 44 | div [ ClassName Style.ToggleButton ] [ ofList buttons ] 45 | ] 46 | -------------------------------------------------------------------------------- /src/client/fsharp/State.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.State 2 | 3 | open FantomasTools.Client 4 | open Elmish 5 | open FantomasTools.Client.Model 6 | open Thoth.Json 7 | open Feliz.Router 8 | 9 | let private getBubbleFromUrl () : BubbleModel = 10 | let empty = 11 | { SourceCode = "" 12 | IsFsi = false 13 | Defines = "" 14 | ResultCode = "" 15 | Diagnostics = Array.empty 16 | HighLight = Range.Zero } 17 | 18 | UrlTools.restoreModelFromUrl 19 | (Decode.object (fun get -> 20 | let sourceCode = get.Required.Field "code" Decode.string 21 | let isFsi = get.Optional.Field "isFsi" Decode.bool |> Option.defaultValue false 22 | let defines = get.Optional.Field "defines" Decode.string |> Option.defaultValue "" 23 | 24 | { empty with 25 | SourceCode = sourceCode 26 | IsFsi = isFsi 27 | Defines = defines })) 28 | empty 29 | 30 | let private getIsFsiFileFromUrl () = 31 | UrlTools.restoreModelFromUrl (Decode.object (fun get -> get.Required.Field "isFsi" Decode.bool)) false 32 | 33 | let init _ = 34 | let bubble = getBubbleFromUrl () 35 | let currentTab = Navigation.parseUrl (Router.currentUrl ()) 36 | 37 | let astModel, astCmd = ASTViewer.State.init (currentTab = ASTTab) 38 | let oakModel, oakCmd = OakViewer.State.init () 39 | 40 | let fantomasModel, fantomasCmd = 41 | let tab = 42 | match currentTab with 43 | | ActiveTab.FantomasTab ft -> ft 44 | | _ -> FantomasTools.Client.FantomasOnline.Model.Main 45 | 46 | FantomasOnline.State.init tab 47 | 48 | let model = 49 | { ActiveTab = currentTab 50 | SettingsOpen = false 51 | Bubble = bubble 52 | OakModel = oakModel 53 | ASTModel = astModel 54 | FantomasModel = fantomasModel } 55 | 56 | let cmd = 57 | Cmd.batch 58 | [ Cmd.map ASTMsg astCmd 59 | Cmd.map OakMsg oakCmd 60 | Cmd.map FantomasMsg fantomasCmd ] 61 | 62 | model, cmd 63 | 64 | let private reload model = 65 | if not model.SettingsOpen then 66 | match model.ActiveTab with 67 | | ASTTab -> Cmd.ofMsg FantomasTools.Client.ASTViewer.Model.DoParse |> Cmd.map ASTMsg 68 | | FantomasTab _ -> 69 | Cmd.ofMsg FantomasTools.Client.FantomasOnline.Model.Format 70 | |> Cmd.map FantomasMsg 71 | | OakTab -> Cmd.ofMsg FantomasTools.Client.OakViewer.Model.GetOak |> Cmd.map OakMsg 72 | | _ -> Cmd.none 73 | else 74 | Cmd.none 75 | 76 | let update msg model = 77 | match msg with 78 | | SelectTab tab -> 79 | let nextModel = 80 | match tab with 81 | | ActiveTab.FantomasTab ft when (ft <> model.FantomasModel.Mode) -> 82 | { model with 83 | ActiveTab = tab 84 | Bubble = 85 | { model.Bubble with 86 | Diagnostics = Array.empty } 87 | FantomasModel = { model.FantomasModel with Mode = ft } } 88 | | _ -> 89 | { model with 90 | ActiveTab = tab 91 | Bubble = 92 | { model.Bubble with 93 | Diagnostics = Array.empty } } 94 | 95 | let cmd = Navigation.cmdForCurrentTab tab model 96 | 97 | nextModel, cmd 98 | | UpdateSourceCode code -> 99 | let bubble = { model.Bubble with SourceCode = code } 100 | { model with Bubble = bubble }, Cmd.none 101 | | ToggleSettings -> 102 | let m = 103 | { model with 104 | SettingsOpen = not model.SettingsOpen } 105 | 106 | m, reload m 107 | 108 | | ASTMsg(ASTViewer.Model.Msg.Bubble bubbleMsg) 109 | | OakMsg(OakViewer.Model.Msg.Bubble bubbleMsg) 110 | | FantomasMsg(FantomasOnline.Model.Msg.Bubble bubbleMsg) -> 111 | let bubble, cmd = 112 | match bubbleMsg with 113 | | SetSourceCode code -> { model.Bubble with SourceCode = code }, Cmd.none 114 | | SetFsi isFsiFile -> { model.Bubble with IsFsi = isFsiFile }, Cmd.none 115 | | SetDefines defines -> { model.Bubble with Defines = defines }, Cmd.none 116 | | SetResultCode code -> { model.Bubble with ResultCode = code }, Cmd.none 117 | | SetDiagnostics diagnostics -> 118 | { model.Bubble with 119 | Diagnostics = diagnostics }, 120 | Cmd.none 121 | | HighLight hlr -> { model.Bubble with HighLight = hlr }, Cmd.none 122 | 123 | { model with Bubble = bubble }, cmd 124 | 125 | | OakMsg oMsg -> 126 | let oModel, oCmd = OakViewer.State.update model.Bubble oMsg model.OakModel 127 | 128 | { model with OakModel = oModel }, Cmd.map OakMsg oCmd 129 | 130 | | ASTMsg aMsg -> 131 | let aModel, aCmd = ASTViewer.State.update model.Bubble aMsg model.ASTModel 132 | 133 | { model with ASTModel = aModel }, Cmd.map ASTMsg aCmd 134 | 135 | | FantomasMsg(FantomasOnline.Model.ChangeMode mode) -> 136 | let cmd = 137 | let changeVersion (hashWithoutQuery: string) = 138 | let version m = 139 | match m with 140 | | FantomasOnline.Model.V5 -> "v5" 141 | | FantomasOnline.Model.V6 -> "v6" 142 | | FantomasOnline.Model.V7 -> "v7" 143 | | FantomasOnline.Model.Main -> "main" 144 | | FantomasOnline.Model.Preview -> "preview" 145 | 146 | let oldVersion = version model.FantomasModel.Mode 147 | let newVersion = version mode 148 | hashWithoutQuery.Replace(oldVersion, newVersion) 149 | 150 | Cmd.ofEffect (fun dispatch -> 151 | UrlTools.updateUrlBy changeVersion 152 | dispatch (SelectTab(ActiveTab.FantomasTab(mode)))) 153 | 154 | model, cmd 155 | | FantomasMsg fMsg -> 156 | let isActiveTab = 157 | match model.ActiveTab with 158 | | FantomasTab _ -> true 159 | | _ -> false 160 | 161 | let fModel, fCmd = 162 | FantomasOnline.State.update isActiveTab model.Bubble fMsg model.FantomasModel 163 | 164 | { model with FantomasModel = fModel }, Cmd.map FantomasMsg fCmd 165 | -------------------------------------------------------------------------------- /src/client/fsharp/Style.fs: -------------------------------------------------------------------------------- 1 | namespace FantomasTools.Client 2 | 3 | open Zanaptak.TypedCssClasses 4 | 5 | module private Config = 6 | [] 7 | let cssFile = __SOURCE_DIRECTORY__ + "/../src/styles/style.css" 8 | 9 | type Style = CssClasses 10 | -------------------------------------------------------------------------------- /src/client/fsharp/UrlTools.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.UrlTools 2 | 3 | open Fable.Core.JsInterop 4 | open Fable.Core 5 | open Thoth.Json 6 | open Browser.Types 7 | open Browser 8 | open System 9 | 10 | let private setGetParam (encodedJson: string) : unit = 11 | if not (isNullOrUndefined history.pushState) then 12 | let hashPieces = 13 | window.location.hash.Split([| '?' |], StringSplitOptions.RemoveEmptyEntries) 14 | 15 | let hash = 16 | if 17 | not (isNullOrUndefined hashPieces) 18 | && not (String.IsNullOrWhiteSpace(hashPieces.[0])) 19 | then 20 | hashPieces.[0] 21 | else 22 | "" 23 | 24 | let ``params`` = URLSearchParams.Create() 25 | ``params``.set ("data", encodedJson) 26 | 27 | let newUrl = 28 | $"{window.location.protocol}//{window.location.host}{window.location.pathname}{hash}?{``params``.ToString()}" 29 | 30 | let currentUrl = window.location.toString () 31 | 32 | if currentUrl <> newUrl then 33 | history.pushState ({| path = newUrl |}, "", newUrl) 34 | 35 | let private encodeUrl (_x: string) : string = 36 | import "compressToEncodedURIComponent" "lz-string" 37 | 38 | let private decodeUrl (_x: string) : string = 39 | import "decompressFromEncodedURIComponent" "lz-string" 40 | 41 | let private URLSearchParamsExist: bool = emitJsExpr () "'URLSearchParams' in window" 42 | 43 | let updateUrlBy (mapFn: string -> string) : unit = 44 | if URLSearchParamsExist then 45 | let hashPieces = window.location.hash.Split('?') 46 | let ``params`` = URLSearchParams.Create(hashPieces.[1]) 47 | 48 | let safeHash = 49 | if isNullOrUndefined window.location.hash then 50 | "" 51 | else 52 | window.location.hash 53 | 54 | let newHash = (mapFn safeHash).Split('?').[0] 55 | 56 | let newUrl = 57 | $"{window.location.protocol}//{window.location.host}{window.location.pathname}{newHash}?{``params``.ToString()}" 58 | 59 | history.pushState ({| path = newUrl |}, "", newUrl) 60 | 61 | let updateUrlWithData json = setGetParam (encodeUrl json) 62 | 63 | let private (|KeyValuesFromHash|_|) hash = 64 | if String.IsNullOrWhiteSpace(hash) then 65 | None 66 | else 67 | let search = hash.Split('?') 68 | 69 | if Seq.length search > 1 then 70 | search.[1].Split('&') 71 | |> Array.map (fun kv -> kv.Split('=').[0], kv.Split('=').[1]) 72 | |> Array.choose (fun (k, v) -> if k = "data" then Some v else None) 73 | |> Array.tryHead 74 | else 75 | None 76 | 77 | let restoreModelFromUrl decoder defaultValue = 78 | match Browser.Dom.window.location.hash with 79 | | KeyValuesFromHash v -> 80 | let json = JS.decodeURIComponent v |> decodeUrl 81 | let modelResult = Decode.fromString decoder json 82 | 83 | match modelResult with 84 | | Result.Ok m -> m 85 | | Error err -> 86 | printfn "%A" err 87 | defaultValue 88 | | _ -> defaultValue 89 | -------------------------------------------------------------------------------- /src/client/fsharp/Utils.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.Utils 2 | 3 | let memoizeBy (g: 'a -> 'c) (f: 'a -> 'b) = 4 | let cache = System.Collections.Generic.Dictionary<_, _>() 5 | 6 | fun x -> 7 | let key = g x 8 | 9 | if cache.ContainsKey key then 10 | cache[key] 11 | else 12 | let y = f x 13 | cache.Add(key, y) 14 | y 15 | 16 | let inline memoize f = memoizeBy id f 17 | 18 | let inline memoize2 f = 19 | memoizeBy id (fun (x, y) -> f x y) |> fun f -> fun x y -> f (x, y) 20 | -------------------------------------------------------------------------------- /src/client/fsharp/VersionBar.fs: -------------------------------------------------------------------------------- 1 | module FantomasTools.Client.VersionBar 2 | 3 | open Fable.React 4 | open Fable.React.Props 5 | open FantomasTools.Client 6 | 7 | let versionBar version = 8 | div [ ClassName Style.VersionBar ] [ str version ] 9 | -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Fantomas Tools 13 | 14 | 15 |
16 | 17 | 18 | 19 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "bunx --bun vite", 4 | "build": "bunx --bun vite build", 5 | "serve": "bunx --bun vite preview", 6 | "format": "prettier --write \"src/**/*.{js,jsx}\" vite.config.js", 7 | "lint": "prettier --check \"src/**/*.{js,jsx}\" vite.config.js" 8 | }, 9 | "type": "module", 10 | "dependencies": { 11 | "@monaco-editor/react": "4.6.0", 12 | "lz-string": "1.4.4", 13 | "notyf": "3.10.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "use-sync-external-store": "^1.2.0", 17 | "vis-data": "7.1.8", 18 | "vis-network": "9.1.9" 19 | }, 20 | "devDependencies": { 21 | "@vitejs/plugin-react": "4.2.1", 22 | "prettier": "3.1.1", 23 | "prop-types": "^15.8.1", 24 | "vite": "^5.2.0-beta.1", 25 | "vite-plugin-fable": "^0.0.26" 26 | }, 27 | "trustedDependencies": [ 28 | "vite-plugin-fable" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/client/public/debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/client/public/fantomas_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/fantomas-tools/a3382db67bcca1bf88988ba4e76741e845be7f99/src/client/public/fantomas_logo.png -------------------------------------------------------------------------------- /src/client/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/fantomas-tools/a3382db67bcca1bf88988ba4e76741e845be7f99/src/client/public/logo.png -------------------------------------------------------------------------------- /src/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/client/src/index.jsx: -------------------------------------------------------------------------------- 1 | import '../fsharp/App.fs'; 2 | import './styles/style.css'; 3 | import 'notyf/notyf.min.css'; 4 | import 'vis-network/styles/vis-network.css'; 5 | -------------------------------------------------------------------------------- /src/client/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { defineConfig } from 'vite'; 4 | import react from '@vitejs/plugin-react'; 5 | import fable from 'vite-plugin-fable'; 6 | 7 | const currentDir = path.dirname(fileURLToPath(import.meta.url)); 8 | const fsproj = path.join(currentDir, 'fsharp/FantomasTools.fsproj'); 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | plugins: [react({ jsxRuntime: 'classic' }), fable({ fsproj })], 13 | server: { 14 | port: 9060, 15 | }, 16 | build: { 17 | outDir: 'build', 18 | }, 19 | base: '/fantomas-tools/', 20 | preview: { 21 | port: 9060, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/server/ASTViewer/ASTViewer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | true 5 | Lambda 6 | exe 7 | 8 | true 9 | false 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/server/ASTViewer/Decoders.fs: -------------------------------------------------------------------------------- 1 | module ASTViewer.Server.Decoders 2 | 3 | open Thoth.Json.Net 4 | open ASTViewer.Shared 5 | 6 | let private decodeInput = 7 | Decode.object (fun get -> 8 | { SourceCode = get.Required.Field "sourceCode" Decode.string 9 | Defines = get.Required.Field "defines" (Decode.array Decode.string) 10 | IsFsi = get.Required.Field "isFsi" Decode.bool 11 | Expand = get.Required.Field "expand" Decode.bool }) 12 | 13 | let decodeInputRequest json = Decode.fromString decodeInput json 14 | -------------------------------------------------------------------------------- /src/server/ASTViewer/Encoders.fs: -------------------------------------------------------------------------------- 1 | module ASTViewer.Server.Encoders 2 | 3 | open Fantomas.FCS.Diagnostics 4 | open Fantomas.FCS.Text 5 | open Fantomas.FCS.Parse 6 | open Thoth.Json.Net 7 | 8 | let private mkRange (range: Range) : FantomasTools.Client.Range = 9 | { StartLine = range.StartLine 10 | StartColumn = range.StartColumn 11 | EndLine = range.EndLine 12 | EndColumn = range.EndColumn } 13 | 14 | let private fsharpErrorInfoSeverity = 15 | function 16 | | FSharpDiagnosticSeverity.Warning -> "warning" 17 | | FSharpDiagnosticSeverity.Error -> "error" 18 | | FSharpDiagnosticSeverity.Hidden -> "hidden" 19 | | FSharpDiagnosticSeverity.Info -> "info" 20 | 21 | let private encodeFSharpParserDiagnostic (info: FSharpParserDiagnostic) = 22 | ({ SubCategory = info.SubCategory 23 | Range = 24 | match info.Range with 25 | | None -> mkRange Range.Zero 26 | | Some r -> mkRange r 27 | Severity = fsharpErrorInfoSeverity info.Severity 28 | ErrorNumber = Option.defaultValue 0 info.ErrorNumber 29 | Message = info.Message } 30 | : FantomasTools.Client.Diagnostic) 31 | |> FantomasTools.Client.Diagnostic.Encode 32 | 33 | let encodeResponse ast (diagnostics: FSharpParserDiagnostic list) = 34 | let errors = List.map encodeFSharpParserDiagnostic diagnostics |> Encode.list 35 | Encode.object [ "ast", Encode.string ast; "diagnostics", errors ] 36 | -------------------------------------------------------------------------------- /src/server/ASTViewer/ExpandedAST.fsi: -------------------------------------------------------------------------------- 1 | module ASTViewer.ExpandedAST 2 | 3 | open Fantomas.FCS.Syntax 4 | 5 | /// Process the ParsedInput tree using reflection to produce a rich "ToString" representation. 6 | /// This string comes very close to usable input if one were to construct the AST manually. 7 | /// Could be slow for large trees and might have some tail recursion problems. 8 | val getExpandedAST: ast: ParsedInput -> string 9 | -------------------------------------------------------------------------------- /src/server/ASTViewer/GetAST.fs: -------------------------------------------------------------------------------- 1 | module ASTViewer.GetAST 2 | 3 | open System.Reflection 4 | open Thoth.Json.Net 5 | open Fantomas.FCS.Text 6 | open Fantomas.FCS.Parse 7 | open ASTViewer.Shared 8 | open ASTViewer.Server 9 | 10 | module Const = 11 | let sourceSizeLimit = 100 * 1024 12 | 13 | let getVersion () = 14 | let assembly = typeof.Assembly 15 | 16 | match Option.ofObj (assembly.GetCustomAttribute()) with 17 | | Some attr -> attr.InformationalVersion 18 | | None -> 19 | let version = assembly.GetName().Version 20 | sprintf "%i.%i.%i" version.Major version.Minor version.Revision 21 | 22 | [] 23 | type ASTResponse = 24 | | Ok of json: string 25 | | TooLarge 26 | | InternalError of string 27 | 28 | let getUntypedAST json : ASTResponse = 29 | let parseRequest = Decoders.decodeInputRequest json 30 | 31 | match parseRequest with 32 | | Ok input when (input.SourceCode.Length < Const.sourceSizeLimit) -> 33 | let ast, errors = 34 | parseFile input.IsFsi (SourceText.ofString input.SourceCode) (List.ofArray input.Defines) 35 | 36 | let astString = 37 | if input.Expand then 38 | try 39 | ExpandedAST.getExpandedAST ast 40 | with ex -> 41 | $"Failed to expand AST, please contribute a fix for this.\nError:%s{ex.Message}" 42 | else 43 | $"%A{ast}" 44 | 45 | Encoders.encodeResponse astString errors |> Encode.toString 2 |> ASTResponse.Ok 46 | 47 | | Ok _ -> ASTResponse.TooLarge 48 | | Error err -> ASTResponse.InternalError $"%A{err}" 49 | -------------------------------------------------------------------------------- /src/server/ASTViewer/Lambda.fs: -------------------------------------------------------------------------------- 1 | module ASTViewer.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open ASTViewer.GetAST 8 | open HttpConstants 9 | 10 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 11 | [)>] 12 | () 13 | 14 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 15 | let version = getVersion () 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, version) 17 | 18 | let private mapASTResponse response = 19 | match response with 20 | | ASTResponse.Ok json -> HttpStatusCode.OK, HeaderValues.ApplicationJson, json 21 | | ASTResponse.TooLarge -> HttpStatusCode.RequestEntityTooLarge, HeaderValues.ApplicationText, "File was too large" 22 | | ASTResponse.InternalError error -> HttpStatusCode.InternalServerError, HeaderValues.ApplicationText, error 23 | 24 | let PostUntypedAST (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 25 | let astResponse = getUntypedAST request.Body 26 | 27 | mapASTResponse astResponse |> mkAPIGatewayProxyResponse 28 | -------------------------------------------------------------------------------- /src/server/ASTViewer/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open SuaveExtensions 6 | open ASTViewer.GetAST 7 | 8 | [] 9 | let main argv = 10 | let mapASTResponseToWebPart (response: ASTResponse) : WebPart = 11 | match response with 12 | | ASTResponse.Ok body -> (applicationJson >=> OK body) 13 | | ASTResponse.TooLarge -> (applicationText >=> REQUEST_ENTITY_TOO_LARGE "File was too large") 14 | | ASTResponse.InternalError error -> (applicationText >=> INTERNAL_SERVER_ERROR error) 15 | 16 | let untypedAst = 17 | request (fun req ctx -> 18 | async { 19 | let json = req.BodyText 20 | let astResponse = getUntypedAST json 21 | return! (mapASTResponseToWebPart astResponse) ctx 22 | }) 23 | 24 | let routes = 25 | [ GET >=> path "/ast-viewer/version" >=> textPlain >=> OK(getVersion ()) 26 | POST >=> path "/ast-viewer/untyped-ast" >=> untypedAst ] 27 | 28 | let port = 29 | match List.ofArray argv with 30 | | [ "--port"; port ] -> System.UInt16.Parse port 31 | | _ -> 7412us 32 | 33 | startFantomasTool port routes 34 | 35 | 0 36 | -------------------------------------------------------------------------------- /src/server/ASTViewer/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net8.0": { 5 | "Amazon.Lambda.APIGatewayEvents": { 6 | "type": "Direct", 7 | "requested": "[2.5.0, )", 8 | "resolved": "2.5.0", 9 | "contentHash": "u2M1e8e+eahgwSpa2jhBaakH37EgIZcHqmqpK/9DD/PMygxK5g7LPyUl6SRFnVnmVyD0zvjEh8lYnpYULY6WIQ==" 10 | }, 11 | "Amazon.Lambda.Core": { 12 | "type": "Direct", 13 | "requested": "[2.1.0, )", 14 | "resolved": "2.1.0", 15 | "contentHash": "ok06UhfBZw6j6+PhiJR9C0EOMuJvnq8rMCHVkaFmPFrlI/q447ukwGZQKaAKqodV+MNTfpb/iPxjgUPVbSlVVw==" 16 | }, 17 | "Amazon.Lambda.Serialization.SystemTextJson": { 18 | "type": "Direct", 19 | "requested": "[2.3.0, )", 20 | "resolved": "2.3.0", 21 | "contentHash": "qgFCDJp5lyUNqCq1z18U7fZ/+rmMyw6RJf9nKfnJrf79YupDj02klQAjxymEYN66NykWXyc68SGkow6fy53hfg==", 22 | "dependencies": { 23 | "Amazon.Lambda.Core": "2.1.0" 24 | } 25 | }, 26 | "FSharp.Core": { 27 | "type": "Direct", 28 | "requested": "[8.0.403, )", 29 | "resolved": "8.0.403", 30 | "contentHash": "t1Pvv2++3zMQKKNuiQc1Lz4TCdaBajgG4mLhOE8AoFzborHQ/JbjIaJr6Mrq8m2z15KLu4r6Qz7E3oeACpljTg==" 31 | }, 32 | "Microsoft.Net.Http.Headers": { 33 | "type": "Direct", 34 | "requested": "[2.2.8, )", 35 | "resolved": "2.2.8", 36 | "contentHash": "wHdwMv0QDDG2NWDSwax9cjkeQceGC1Qq53a31+31XpvTXVljKXRjWISlMoS/wZYKiqdqzuEvKFKwGHl+mt2jCA==", 37 | "dependencies": { 38 | "Microsoft.Extensions.Primitives": "2.2.0", 39 | "System.Buffers": "4.5.0" 40 | } 41 | }, 42 | "Suave": { 43 | "type": "Direct", 44 | "requested": "[2.6.2, )", 45 | "resolved": "2.6.2", 46 | "contentHash": "JNTsgb3FrFnvsp7G93Y9XLIGVa47fG4GZ8un/+/iMMVBTRWl8l6Rlnqjo/PsQP6NojtkrLEM4SLz9AhfQIPNag==", 47 | "dependencies": { 48 | "FSharp.Core": "0.0.0" 49 | } 50 | }, 51 | "Thoth.Json.Net": { 52 | "type": "Direct", 53 | "requested": "[8.0.0, )", 54 | "resolved": "8.0.0", 55 | "contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==", 56 | "dependencies": { 57 | "FSharp.Core": "4.7.2", 58 | "Fable.Core": "[3.0.0, 4.0.0)", 59 | "Newtonsoft.Json": "11.0.2" 60 | } 61 | }, 62 | "Fable.Core": { 63 | "type": "Transitive", 64 | "resolved": "3.0.0", 65 | "contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==", 66 | "dependencies": { 67 | "FSharp.Core": "4.5.2" 68 | } 69 | }, 70 | "Microsoft.Extensions.Primitives": { 71 | "type": "Transitive", 72 | "resolved": "2.2.0", 73 | "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", 74 | "dependencies": { 75 | "System.Memory": "4.5.1", 76 | "System.Runtime.CompilerServices.Unsafe": "4.5.1" 77 | } 78 | }, 79 | "Microsoft.NETCore.Platforms": { 80 | "type": "Transitive", 81 | "resolved": "1.1.1", 82 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 83 | }, 84 | "Microsoft.NETCore.Targets": { 85 | "type": "Transitive", 86 | "resolved": "1.1.3", 87 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 88 | }, 89 | "Newtonsoft.Json": { 90 | "type": "Transitive", 91 | "resolved": "11.0.2", 92 | "contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==" 93 | }, 94 | "System.Buffers": { 95 | "type": "Transitive", 96 | "resolved": "4.5.0", 97 | "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" 98 | }, 99 | "System.Collections.Immutable": { 100 | "type": "Transitive", 101 | "resolved": "8.0.0", 102 | "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" 103 | }, 104 | "System.Diagnostics.DiagnosticSource": { 105 | "type": "Transitive", 106 | "resolved": "8.0.1", 107 | "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" 108 | }, 109 | "System.Memory": { 110 | "type": "Transitive", 111 | "resolved": "4.6.0", 112 | "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==" 113 | }, 114 | "System.Runtime": { 115 | "type": "Transitive", 116 | "resolved": "4.3.1", 117 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 118 | "dependencies": { 119 | "Microsoft.NETCore.Platforms": "1.1.1", 120 | "Microsoft.NETCore.Targets": "1.1.3" 121 | } 122 | }, 123 | "System.Runtime.CompilerServices.Unsafe": { 124 | "type": "Transitive", 125 | "resolved": "4.5.1", 126 | "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" 127 | }, 128 | "fantomas.core": { 129 | "type": "Project", 130 | "dependencies": { 131 | "FSharp.Core": "[8.0.100, )", 132 | "Fantomas.FCS": "[1.0.0, )" 133 | } 134 | }, 135 | "fantomas.fcs": { 136 | "type": "Project", 137 | "dependencies": { 138 | "FSharp.Core": "[8.0.100, )", 139 | "System.Collections.Immutable": "[8.0.0, )", 140 | "System.Diagnostics.DiagnosticSource": "[8.0.1, )", 141 | "System.Memory": "[4.6.0, )", 142 | "System.Runtime": "[4.3.1, )" 143 | } 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/server/AWSLambdaExtensions.fs: -------------------------------------------------------------------------------- 1 | module AWSLambdaExtensions 2 | 3 | open System.Collections.Generic 4 | open System.Net 5 | open Amazon.Lambda.APIGatewayEvents 6 | open Microsoft.Net.Http.Headers 7 | 8 | let createHeaders headers = 9 | Seq.fold 10 | (fun (acc: Dictionary) (key, value) -> 11 | acc.[key] <- value 12 | acc) 13 | (Dictionary()) 14 | headers 15 | 16 | let mkAPIGatewayProxyResponse (statusCode: HttpStatusCode, contentTypeHeaderValue: string, body: string) = 17 | APIGatewayProxyResponse( 18 | StatusCode = int statusCode, 19 | Body = body, 20 | Headers = createHeaders [ HeaderNames.ContentType, contentTypeHeaderValue ] 21 | ) 22 | -------------------------------------------------------------------------------- /src/server/FantomasOnline.Shared/Decoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnline.Server.Shared.Decoders 2 | 3 | open FantomasOnline.Shared 4 | open Thoth.Json.Net 5 | 6 | let optionDecoder: Decoder = 7 | Decode.object (fun get -> 8 | let t = get.Required.Field "$type" Decode.string 9 | 10 | if t = "int" then 11 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.int) 12 | |> FantomasOption.IntOption 13 | elif t = "bool" then 14 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.bool) 15 | |> FantomasOption.BoolOption 16 | elif t = "multilineFormatterType" then 17 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.string) 18 | |> FantomasOption.MultilineFormatterTypeOption 19 | elif t = "endOfLineStyle" then 20 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.string) 21 | |> FantomasOption.EndOfLineStyleOption 22 | elif t = "multilineBracketStyle" then 23 | get.Required.Field "$value" (Decode.tuple3 Decode.int Decode.string Decode.string) 24 | |> FantomasOption.MultilineBracketStyleOption 25 | else 26 | failwithf $"Could not decode %s{t}") 27 | 28 | let requestDecoder: Decoder = 29 | Decode.object (fun get -> 30 | { SourceCode = get.Required.Field "sourceCode" Decode.string 31 | Options = get.Required.Field "options" (Decode.list optionDecoder |> Decode.map (List.sortBy sortByOption)) 32 | IsFsi = get.Required.Field "isFsi" Decode.bool }) 33 | 34 | let decodeRequest json = Decode.fromString requestDecoder json 35 | -------------------------------------------------------------------------------- /src/server/FantomasOnline.Shared/Encoders.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnline.Server.Shared.Encoders 2 | 3 | open Thoth.Json.Net 4 | open FantomasOnline.Shared 5 | open FantomasTools.Client 6 | 7 | let encodeOptions options = 8 | options 9 | |> List.toArray 10 | |> Array.map (fun option -> 11 | match option with 12 | | IntOption(o, k, i) -> 13 | Encode.object 14 | [ "$type", Encode.string "int" 15 | "$value", Encode.tuple3 Encode.int Encode.string Encode.int (o, k, i) ] 16 | | BoolOption(o, k, b) -> 17 | Encode.object 18 | [ "$type", Encode.string "bool" 19 | "$value", Encode.tuple3 Encode.int Encode.string Encode.bool (o, k, b) ] 20 | | MultilineFormatterTypeOption(o, k, v) -> 21 | Encode.object 22 | [ "$type", Encode.string "multilineFormatterType" 23 | "$value", Encode.tuple3 Encode.int Encode.string Encode.string (o, k, v) ] 24 | | EndOfLineStyleOption(o, k, v) -> 25 | Encode.object 26 | [ "$type", Encode.string "endOfLineStyle" 27 | "$value", Encode.tuple3 Encode.int Encode.string Encode.string (o, k, v) ] 28 | | MultilineBracketStyleOption(o, k, v) -> 29 | Encode.object 30 | [ "$type", Encode.string "multilineBracketStyle" 31 | "$value", Encode.tuple3 Encode.int Encode.string Encode.string (o, k, v) ]) 32 | |> Encode.array 33 | |> Encode.toString 4 34 | 35 | let encodeFormatResponse (formatResponse: FormatResponse) = 36 | Encode.object 37 | [ "firstFormat", Encode.string formatResponse.FirstFormat 38 | "firstValidation", (formatResponse.FirstValidation |> Array.map Diagnostic.Encode |> Encode.array) 39 | "secondFormat", Encode.option Encode.string formatResponse.SecondFormat 40 | "secondValidation", (formatResponse.SecondValidation |> Array.map Diagnostic.Encode |> Encode.array) ] 41 | -------------------------------------------------------------------------------- /src/server/FantomasOnline.Shared/Http.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnline.Server.Shared.Http 2 | 3 | open System.Net 4 | open FantomasTools.Client 5 | open Thoth.Json.Net 6 | open HttpConstants 7 | open AWSLambdaExtensions 8 | open FantomasOnline.Server.Shared 9 | open FantomasOnline.Shared 10 | 11 | module Reflection = 12 | open FSharp.Reflection 13 | 14 | let getRecordFields x = 15 | let names = FSharpType.GetRecordFields(x.GetType()) |> Seq.map (fun x -> x.Name) 16 | 17 | let values = FSharpValue.GetRecordFields x 18 | Seq.zip names values |> Seq.toArray 19 | 20 | let mapOptionsToJson (options: FantomasOption list) = options |> Encoders.encodeOptions 21 | 22 | [] 23 | type FormatResponse = 24 | | Ok of json: string 25 | | BadRequest of error: string 26 | | InternalError of error: string 27 | 28 | let formatCode 29 | (mapFantomasOptionsToRecord: FantomasOption list -> 'options) 30 | (format: string -> string -> 'options -> Async) 31 | (validate: string -> string -> Async) 32 | (json: string) 33 | : Async 34 | = 35 | async { 36 | let model = Decoders.decodeRequest json 37 | 38 | let configResult = 39 | Result.map (fun r -> r, mapFantomasOptionsToRecord r.Options) model 40 | 41 | match configResult with 42 | | Ok({ SourceCode = code; IsFsi = isFsi }, config) -> 43 | let fileName = if isFsi then "tmp.fsi" else "tmp.fsx" 44 | 45 | try 46 | let! firstFormat = format fileName code config 47 | let! firstValidation = validate fileName firstFormat 48 | 49 | let! secondFormat, secondValidation = 50 | if not (List.isEmpty firstValidation) then 51 | async.Return(None, []) 52 | else 53 | async { 54 | let! secondFormat = format fileName firstFormat config 55 | let! secondValidation = validate fileName secondFormat 56 | return (Some secondFormat, secondValidation) 57 | } 58 | 59 | let response = 60 | { FirstFormat = firstFormat 61 | FirstValidation = Array.ofList firstValidation 62 | SecondFormat = secondFormat 63 | SecondValidation = Array.ofList secondValidation } 64 | |> Encoders.encodeFormatResponse 65 | |> Encode.toString 4 66 | 67 | return FormatResponse.Ok response 68 | with exn -> 69 | return FormatResponse.InternalError(string exn) 70 | | Error err -> return FormatResponse.BadRequest err 71 | } 72 | 73 | let mapFormatResponseToAPIGatewayProxyResponse (response: FormatResponse) = 74 | match response with 75 | | FormatResponse.Ok json -> HttpStatusCode.OK, HeaderValues.ApplicationJson, json 76 | | FormatResponse.BadRequest error -> HttpStatusCode.BadRequest, HeaderValues.ApplicationText, error 77 | | FormatResponse.InternalError error -> HttpStatusCode.InternalServerError, HeaderValues.ApplicationText, error 78 | |> mkAPIGatewayProxyResponse 79 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/FantomasOnlineMain.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | Lambda 7 | exe 8 | 9 | true 10 | false 11 | true 12 | FantomasOnlineMain 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/FormatCode.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineMain.FormatCode 2 | 3 | open Fantomas.FCS.Text 4 | open Fantomas.Core 5 | open FantomasOnline.Shared 6 | open FantomasOnline.Server.Shared.Http 7 | open FantomasTools.Client 8 | 9 | let private mapFantomasOptionsToRecord options = 10 | let newValues = 11 | options 12 | |> Seq.map (function 13 | | BoolOption(_, _, v) -> box v 14 | | IntOption(_, _, v) -> box v 15 | | MultilineFormatterTypeOption(_, _, v) -> 16 | MultilineFormatterType.OfConfigString(v) 17 | |> Option.defaultValue CharacterWidth 18 | |> box 19 | | EndOfLineStyleOption(_, _, v) -> 20 | EndOfLineStyle.OfConfigString(v) 21 | |> Option.defaultValue EndOfLineStyle.CRLF 22 | |> box 23 | | MultilineBracketStyleOption(_, _, v) -> 24 | MultilineBracketStyle.OfConfigString(v) 25 | |> Option.defaultValue MultilineBracketStyle.Cramped 26 | |> box) 27 | |> Seq.toArray 28 | 29 | let formatConfigType = typeof 30 | Microsoft.FSharp.Reflection.FSharpValue.MakeRecord(formatConfigType, newValues) :?> FormatConfig 31 | 32 | let private format (fileName: string) code config = 33 | let isSignature = fileName.EndsWith(".fsi") 34 | 35 | async { 36 | let! result = CodeFormatter.FormatDocumentAsync(isSignature, code, config) 37 | return result.Code 38 | } 39 | 40 | let private validate (fileName: string) code = 41 | let isSignature = fileName.EndsWith(".fsi") 42 | let sourceCode = SourceText.ofString code 43 | let _, diagnostics = Fantomas.FCS.Parse.parseFile isSignature sourceCode [] 44 | 45 | diagnostics 46 | |> List.map (fun e -> 47 | let range = 48 | match e.Range with 49 | | None -> 50 | { StartLine = 0 51 | StartColumn = 0 52 | EndLine = 0 53 | EndColumn = 0 } 54 | | Some r -> 55 | { StartLine = r.StartLine 56 | StartColumn = r.StartColumn 57 | EndLine = r.EndLine 58 | EndColumn = r.EndColumn } 59 | 60 | { SubCategory = e.SubCategory 61 | Range = range 62 | Severity = $"{e.Severity}".ToLower() 63 | ErrorNumber = Option.defaultValue -1 e.ErrorNumber 64 | Message = e.Message } 65 | : Diagnostic) 66 | |> fun errors -> async { return errors } 67 | 68 | let getVersion () = 69 | let date = 70 | let lastCommitInfo = 71 | sprintf 72 | "%s - %s" 73 | (System.Environment.GetEnvironmentVariable("LAST_COMMIT_TIMESTAMP")) 74 | (System.Environment.GetEnvironmentVariable("LAST_COMMIT_SHA")) 75 | 76 | if lastCommitInfo.Trim() <> "-" then 77 | lastCommitInfo 78 | else 79 | let assembly = typeof.Assembly 80 | 81 | System.IO.FileInfo assembly.Location 82 | |> fun f -> f.LastWriteTime.ToShortDateString() 83 | 84 | $"main branch at %s{date}" 85 | 86 | let getOptions () : string = 87 | Reflection.getRecordFields FormatConfig.Default 88 | |> Seq.indexed 89 | |> Seq.choose (fun (idx, (k: string, v: obj)) -> 90 | match v with 91 | | :? int as i -> FantomasOption.IntOption(idx, k, i) |> Some 92 | | :? bool as b -> FantomasOption.BoolOption(idx, k, b) |> Some 93 | | :? MultilineFormatterType as mft -> 94 | FantomasOption.MultilineFormatterTypeOption(idx, k, (MultilineFormatterType.ToConfigString mft)) 95 | |> Some 96 | | :? EndOfLineStyle as eol -> 97 | FantomasOption.EndOfLineStyleOption(idx, k, (EndOfLineStyle.ToConfigString eol)) 98 | |> Some 99 | | :? MultilineBracketStyle as mbs -> 100 | FantomasOption.MultilineBracketStyleOption(idx, k, (MultilineBracketStyle.ToConfigString mbs)) 101 | |> Some 102 | | _ -> None) 103 | |> Seq.toList 104 | |> mapOptionsToJson 105 | 106 | let formatCode: string -> Async = 107 | formatCode mapFantomasOptionsToRecord format validate 108 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/Lambda.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineMain.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open HttpConstants 8 | open FantomasOnline.Server.Shared.Http 9 | open FantomasOnlineMain.FormatCode 10 | 11 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 12 | [)>] 13 | () 14 | 15 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, getVersion ()) 17 | 18 | let GetOptions (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 19 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.ApplicationJson, getOptions ()) 20 | 21 | let PostFormat (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 22 | async { 23 | let! response = formatCode request.Body 24 | return mapFormatResponseToAPIGatewayProxyResponse response 25 | } 26 | |> Async.StartAsTask 27 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open Suave.RequestErrors 6 | open SuaveExtensions 7 | open FantomasOnline.Server.Shared.Http 8 | 9 | [] 10 | let main argv = 11 | let mapFormatResponseToWebPart (response: FormatResponse) : WebPart = 12 | match response with 13 | | FormatResponse.Ok body -> (applicationJson >=> OK body) 14 | | FormatResponse.BadRequest error -> (applicationText >=> BAD_REQUEST error) 15 | | FormatResponse.InternalError error -> (applicationText >=> INTERNAL_SERVER_ERROR error) 16 | 17 | let formatWebPart = 18 | request (fun req ctx -> 19 | async { 20 | let json = req.BodyText 21 | let! formatResponse = FantomasOnlineMain.FormatCode.formatCode json 22 | return! (mapFormatResponseToWebPart formatResponse) ctx 23 | }) 24 | 25 | let routes = 26 | [ GET >=> path "/fantomas/main/version" >=> textPlain >=> OK(FantomasOnlineMain.FormatCode.getVersion ()) 27 | GET >=> path "/fantomas/main/options" >=> applicationJson >=> OK(FantomasOnlineMain.FormatCode.getOptions ()) 28 | POST >=> path "/fantomas/main/format" >=> formatWebPart ] 29 | 30 | let port = 31 | match List.ofArray argv with 32 | | [ "--port"; port ] -> System.UInt16.Parse port 33 | | _ -> 11084us 34 | 35 | startFantomasTool port routes 36 | 37 | 0 38 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlineMain/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net8.0": { 5 | "Amazon.Lambda.APIGatewayEvents": { 6 | "type": "Direct", 7 | "requested": "[2.5.0, )", 8 | "resolved": "2.5.0", 9 | "contentHash": "u2M1e8e+eahgwSpa2jhBaakH37EgIZcHqmqpK/9DD/PMygxK5g7LPyUl6SRFnVnmVyD0zvjEh8lYnpYULY6WIQ==" 10 | }, 11 | "Amazon.Lambda.Core": { 12 | "type": "Direct", 13 | "requested": "[2.1.0, )", 14 | "resolved": "2.1.0", 15 | "contentHash": "ok06UhfBZw6j6+PhiJR9C0EOMuJvnq8rMCHVkaFmPFrlI/q447ukwGZQKaAKqodV+MNTfpb/iPxjgUPVbSlVVw==" 16 | }, 17 | "Amazon.Lambda.Serialization.SystemTextJson": { 18 | "type": "Direct", 19 | "requested": "[2.3.0, )", 20 | "resolved": "2.3.0", 21 | "contentHash": "qgFCDJp5lyUNqCq1z18U7fZ/+rmMyw6RJf9nKfnJrf79YupDj02klQAjxymEYN66NykWXyc68SGkow6fy53hfg==", 22 | "dependencies": { 23 | "Amazon.Lambda.Core": "2.1.0" 24 | } 25 | }, 26 | "FSharp.Core": { 27 | "type": "Direct", 28 | "requested": "[8.0.400, )", 29 | "resolved": "8.0.400", 30 | "contentHash": "kHMdDDmlZl98tujgHCmL8/HNH9VKbxsRMC9s7wbwr4noR40SSa5D4d00yF8cMK52s8jabVuiLLcrUw9r+PkKDQ==" 31 | }, 32 | "Microsoft.Net.Http.Headers": { 33 | "type": "Direct", 34 | "requested": "[2.2.8, )", 35 | "resolved": "2.2.8", 36 | "contentHash": "wHdwMv0QDDG2NWDSwax9cjkeQceGC1Qq53a31+31XpvTXVljKXRjWISlMoS/wZYKiqdqzuEvKFKwGHl+mt2jCA==", 37 | "dependencies": { 38 | "Microsoft.Extensions.Primitives": "2.2.0", 39 | "System.Buffers": "4.5.0" 40 | } 41 | }, 42 | "Suave": { 43 | "type": "Direct", 44 | "requested": "[2.6.2, )", 45 | "resolved": "2.6.2", 46 | "contentHash": "JNTsgb3FrFnvsp7G93Y9XLIGVa47fG4GZ8un/+/iMMVBTRWl8l6Rlnqjo/PsQP6NojtkrLEM4SLz9AhfQIPNag==", 47 | "dependencies": { 48 | "FSharp.Core": "0.0.0" 49 | } 50 | }, 51 | "Thoth.Json.Net": { 52 | "type": "Direct", 53 | "requested": "[8.0.0, )", 54 | "resolved": "8.0.0", 55 | "contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==", 56 | "dependencies": { 57 | "FSharp.Core": "4.7.2", 58 | "Fable.Core": "[3.0.0, 4.0.0)", 59 | "Newtonsoft.Json": "11.0.2" 60 | } 61 | }, 62 | "Fable.Core": { 63 | "type": "Transitive", 64 | "resolved": "3.0.0", 65 | "contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==", 66 | "dependencies": { 67 | "FSharp.Core": "4.5.2" 68 | } 69 | }, 70 | "Microsoft.Extensions.Primitives": { 71 | "type": "Transitive", 72 | "resolved": "2.2.0", 73 | "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", 74 | "dependencies": { 75 | "System.Memory": "4.5.1", 76 | "System.Runtime.CompilerServices.Unsafe": "4.5.1" 77 | } 78 | }, 79 | "Microsoft.NETCore.Platforms": { 80 | "type": "Transitive", 81 | "resolved": "1.1.1", 82 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 83 | }, 84 | "Microsoft.NETCore.Targets": { 85 | "type": "Transitive", 86 | "resolved": "1.1.3", 87 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 88 | }, 89 | "Newtonsoft.Json": { 90 | "type": "Transitive", 91 | "resolved": "11.0.2", 92 | "contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==" 93 | }, 94 | "System.Buffers": { 95 | "type": "Transitive", 96 | "resolved": "4.5.0", 97 | "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" 98 | }, 99 | "System.Collections.Immutable": { 100 | "type": "Transitive", 101 | "resolved": "8.0.0", 102 | "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" 103 | }, 104 | "System.Diagnostics.DiagnosticSource": { 105 | "type": "Transitive", 106 | "resolved": "8.0.1", 107 | "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" 108 | }, 109 | "System.Memory": { 110 | "type": "Transitive", 111 | "resolved": "4.6.0", 112 | "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==" 113 | }, 114 | "System.Runtime": { 115 | "type": "Transitive", 116 | "resolved": "4.3.1", 117 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 118 | "dependencies": { 119 | "Microsoft.NETCore.Platforms": "1.1.1", 120 | "Microsoft.NETCore.Targets": "1.1.3" 121 | } 122 | }, 123 | "System.Runtime.CompilerServices.Unsafe": { 124 | "type": "Transitive", 125 | "resolved": "4.5.1", 126 | "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" 127 | }, 128 | "fantomas.core": { 129 | "type": "Project", 130 | "dependencies": { 131 | "FSharp.Core": "[8.0.100, )", 132 | "Fantomas.FCS": "[1.0.0, )" 133 | } 134 | }, 135 | "fantomas.fcs": { 136 | "type": "Project", 137 | "dependencies": { 138 | "FSharp.Core": "[8.0.100, )", 139 | "System.Collections.Immutable": "[8.0.0, )", 140 | "System.Diagnostics.DiagnosticSource": "[8.0.1, )", 141 | "System.Memory": "[4.6.0, )", 142 | "System.Runtime": "[4.3.1, )" 143 | } 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/FantomasOnlinePreview.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | Lambda 7 | exe 8 | 9 | true 10 | false 11 | true 12 | FantomasOnlinePreview 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/FormatCode.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlinePreview.FormatCode 2 | 3 | open Fantomas.FCS.Text 4 | open Fantomas.Core 5 | open FantomasOnline.Shared 6 | open FantomasOnline.Server.Shared.Http 7 | open FantomasTools.Client 8 | 9 | let private mapFantomasOptionsToRecord options = 10 | let newValues = 11 | options 12 | |> Seq.map (function 13 | | BoolOption(_, _, v) -> box v 14 | | IntOption(_, _, v) -> box v 15 | | MultilineFormatterTypeOption(_, _, v) -> 16 | MultilineFormatterType.OfConfigString(v) 17 | |> Option.defaultValue CharacterWidth 18 | |> box 19 | | EndOfLineStyleOption(_, _, v) -> 20 | EndOfLineStyle.OfConfigString(v) 21 | |> Option.defaultValue EndOfLineStyle.CRLF 22 | |> box 23 | | MultilineBracketStyleOption(_, _, v) -> 24 | MultilineBracketStyle.OfConfigString(v) 25 | |> Option.defaultValue MultilineBracketStyle.Cramped 26 | |> box) 27 | |> Seq.toArray 28 | 29 | let formatConfigType = typeof 30 | Microsoft.FSharp.Reflection.FSharpValue.MakeRecord(formatConfigType, newValues) :?> FormatConfig 31 | 32 | let private format (fileName: string) code config = 33 | let isSignature = fileName.EndsWith(".fsi") 34 | 35 | async { 36 | let! result = CodeFormatter.FormatDocumentAsync(isSignature, code, config) 37 | return result.Code 38 | } 39 | 40 | let private validate (fileName: string) code = 41 | let isSignature = fileName.EndsWith(".fsi") 42 | let sourceCode = SourceText.ofString code 43 | let _, diagnostics = Fantomas.FCS.Parse.parseFile isSignature sourceCode [] 44 | 45 | diagnostics 46 | |> List.map (fun e -> 47 | let range = 48 | match e.Range with 49 | | None -> Range.Zero 50 | | Some r -> 51 | { StartLine = r.StartLine 52 | StartColumn = r.StartColumn 53 | EndLine = r.EndLine 54 | EndColumn = r.EndColumn } 55 | 56 | { SubCategory = e.SubCategory 57 | Range = range 58 | Severity = $"{e.Severity}".ToLower() 59 | ErrorNumber = Option.defaultValue -1 e.ErrorNumber 60 | Message = e.Message } 61 | : Diagnostic) 62 | |> fun errors -> async { return errors } 63 | 64 | let getVersion () = 65 | let date = 66 | let lastCommitInfo = 67 | sprintf 68 | "%s - %s" 69 | (System.Environment.GetEnvironmentVariable("LAST_COMMIT_TIMESTAMP")) 70 | (System.Environment.GetEnvironmentVariable("LAST_COMMIT_SHA")) 71 | 72 | if lastCommitInfo.Trim() <> "-" then 73 | lastCommitInfo 74 | else 75 | let assembly = typeof.Assembly 76 | 77 | System.IO.FileInfo assembly.Location 78 | |> fun f -> f.LastWriteTime.ToShortDateString() 79 | 80 | $"main branch at %s{date}" 81 | 82 | let getOptions () : string = 83 | Reflection.getRecordFields FormatConfig.Default 84 | |> Seq.indexed 85 | |> Seq.choose (fun (idx, (k: string, v: obj)) -> 86 | match v with 87 | | :? int as i -> FantomasOption.IntOption(idx, k, i) |> Some 88 | | :? bool as b -> FantomasOption.BoolOption(idx, k, b) |> Some 89 | | :? MultilineFormatterType as mft -> 90 | FantomasOption.MultilineFormatterTypeOption(idx, k, (MultilineFormatterType.ToConfigString mft)) 91 | |> Some 92 | | :? EndOfLineStyle as eol -> 93 | FantomasOption.EndOfLineStyleOption(idx, k, (EndOfLineStyle.ToConfigString eol)) 94 | |> Some 95 | | :? MultilineBracketStyle as mbs -> 96 | FantomasOption.MultilineBracketStyleOption(idx, k, (MultilineBracketStyle.ToConfigString mbs)) 97 | |> Some 98 | | _ -> None) 99 | |> Seq.toList 100 | |> mapOptionsToJson 101 | 102 | let formatCode: string -> Async = 103 | formatCode mapFantomasOptionsToRecord format validate 104 | -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/Lambda.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlinePreview.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open HttpConstants 8 | open FantomasOnline.Server.Shared.Http 9 | open FantomasOnlinePreview.FormatCode 10 | 11 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 12 | [)>] 13 | () 14 | 15 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, getVersion ()) 17 | 18 | let GetOptions (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 19 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.ApplicationJson, getOptions ()) 20 | 21 | let PostFormat (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 22 | async { 23 | let! response = formatCode request.Body 24 | return mapFormatResponseToAPIGatewayProxyResponse response 25 | } 26 | |> Async.StartAsTask 27 | -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open Suave.RequestErrors 6 | open SuaveExtensions 7 | open FantomasOnline.Server.Shared.Http 8 | 9 | [] 10 | let main argv = 11 | let mapFormatResponseToWebPart (response: FormatResponse) : WebPart = 12 | match response with 13 | | FormatResponse.Ok body -> (applicationJson >=> OK body) 14 | | FormatResponse.BadRequest error -> (applicationText >=> BAD_REQUEST error) 15 | | FormatResponse.InternalError error -> (applicationText >=> INTERNAL_SERVER_ERROR error) 16 | 17 | let formatWebPart = 18 | request (fun req ctx -> 19 | async { 20 | let json = req.BodyText 21 | let! formatResponse = FantomasOnlinePreview.FormatCode.formatCode json 22 | return! (mapFormatResponseToWebPart formatResponse) ctx 23 | }) 24 | 25 | let routes = 26 | [ GET >=> path "/fantomas/preview/version" >=> textPlain >=> OK(FantomasOnlinePreview.FormatCode.getVersion ()) 27 | GET 28 | >=> path "/fantomas/preview/options" 29 | >=> applicationJson 30 | >=> OK(FantomasOnlinePreview.FormatCode.getOptions ()) 31 | POST >=> path "/fantomas/preview/format" >=> formatWebPart ] 32 | 33 | let port = 34 | match List.ofArray argv with 35 | | [ "--port"; port ] -> System.UInt16.Parse port 36 | | _ -> 12007us 37 | 38 | startFantomasTool port routes 39 | 40 | 0 41 | -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlinePreview/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net8.0": { 5 | "Amazon.Lambda.APIGatewayEvents": { 6 | "type": "Direct", 7 | "requested": "[2.5.0, )", 8 | "resolved": "2.5.0", 9 | "contentHash": "u2M1e8e+eahgwSpa2jhBaakH37EgIZcHqmqpK/9DD/PMygxK5g7LPyUl6SRFnVnmVyD0zvjEh8lYnpYULY6WIQ==" 10 | }, 11 | "Amazon.Lambda.Core": { 12 | "type": "Direct", 13 | "requested": "[2.1.0, )", 14 | "resolved": "2.1.0", 15 | "contentHash": "ok06UhfBZw6j6+PhiJR9C0EOMuJvnq8rMCHVkaFmPFrlI/q447ukwGZQKaAKqodV+MNTfpb/iPxjgUPVbSlVVw==" 16 | }, 17 | "Amazon.Lambda.Serialization.SystemTextJson": { 18 | "type": "Direct", 19 | "requested": "[2.3.0, )", 20 | "resolved": "2.3.0", 21 | "contentHash": "qgFCDJp5lyUNqCq1z18U7fZ/+rmMyw6RJf9nKfnJrf79YupDj02klQAjxymEYN66NykWXyc68SGkow6fy53hfg==", 22 | "dependencies": { 23 | "Amazon.Lambda.Core": "2.1.0" 24 | } 25 | }, 26 | "FSharp.Core": { 27 | "type": "Direct", 28 | "requested": "[8.0.403, )", 29 | "resolved": "8.0.403", 30 | "contentHash": "t1Pvv2++3zMQKKNuiQc1Lz4TCdaBajgG4mLhOE8AoFzborHQ/JbjIaJr6Mrq8m2z15KLu4r6Qz7E3oeACpljTg==" 31 | }, 32 | "Microsoft.Net.Http.Headers": { 33 | "type": "Direct", 34 | "requested": "[2.2.8, )", 35 | "resolved": "2.2.8", 36 | "contentHash": "wHdwMv0QDDG2NWDSwax9cjkeQceGC1Qq53a31+31XpvTXVljKXRjWISlMoS/wZYKiqdqzuEvKFKwGHl+mt2jCA==", 37 | "dependencies": { 38 | "Microsoft.Extensions.Primitives": "2.2.0", 39 | "System.Buffers": "4.5.0" 40 | } 41 | }, 42 | "Suave": { 43 | "type": "Direct", 44 | "requested": "[2.6.2, )", 45 | "resolved": "2.6.2", 46 | "contentHash": "JNTsgb3FrFnvsp7G93Y9XLIGVa47fG4GZ8un/+/iMMVBTRWl8l6Rlnqjo/PsQP6NojtkrLEM4SLz9AhfQIPNag==", 47 | "dependencies": { 48 | "FSharp.Core": "0.0.0" 49 | } 50 | }, 51 | "Thoth.Json.Net": { 52 | "type": "Direct", 53 | "requested": "[8.0.0, )", 54 | "resolved": "8.0.0", 55 | "contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==", 56 | "dependencies": { 57 | "FSharp.Core": "4.7.2", 58 | "Fable.Core": "[3.0.0, 4.0.0)", 59 | "Newtonsoft.Json": "11.0.2" 60 | } 61 | }, 62 | "Fable.Core": { 63 | "type": "Transitive", 64 | "resolved": "3.0.0", 65 | "contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==", 66 | "dependencies": { 67 | "FSharp.Core": "4.5.2" 68 | } 69 | }, 70 | "Microsoft.Extensions.Primitives": { 71 | "type": "Transitive", 72 | "resolved": "2.2.0", 73 | "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", 74 | "dependencies": { 75 | "System.Memory": "4.5.1", 76 | "System.Runtime.CompilerServices.Unsafe": "4.5.1" 77 | } 78 | }, 79 | "Microsoft.NETCore.Platforms": { 80 | "type": "Transitive", 81 | "resolved": "1.1.1", 82 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 83 | }, 84 | "Microsoft.NETCore.Targets": { 85 | "type": "Transitive", 86 | "resolved": "1.1.3", 87 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 88 | }, 89 | "Newtonsoft.Json": { 90 | "type": "Transitive", 91 | "resolved": "11.0.2", 92 | "contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==" 93 | }, 94 | "System.Buffers": { 95 | "type": "Transitive", 96 | "resolved": "4.5.0", 97 | "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" 98 | }, 99 | "System.Collections.Immutable": { 100 | "type": "Transitive", 101 | "resolved": "8.0.0", 102 | "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" 103 | }, 104 | "System.Diagnostics.DiagnosticSource": { 105 | "type": "Transitive", 106 | "resolved": "8.0.1", 107 | "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" 108 | }, 109 | "System.Memory": { 110 | "type": "Transitive", 111 | "resolved": "4.6.0", 112 | "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==" 113 | }, 114 | "System.Runtime": { 115 | "type": "Transitive", 116 | "resolved": "4.3.1", 117 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 118 | "dependencies": { 119 | "Microsoft.NETCore.Platforms": "1.1.1", 120 | "Microsoft.NETCore.Targets": "1.1.3" 121 | } 122 | }, 123 | "System.Runtime.CompilerServices.Unsafe": { 124 | "type": "Transitive", 125 | "resolved": "4.5.1", 126 | "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" 127 | }, 128 | "fantomas.core": { 129 | "type": "Project", 130 | "dependencies": { 131 | "FSharp.Core": "[8.0.100, )", 132 | "Fantomas.FCS": "[1.0.0, )" 133 | } 134 | }, 135 | "fantomas.fcs": { 136 | "type": "Project", 137 | "dependencies": { 138 | "FSharp.Core": "[8.0.100, )", 139 | "System.Collections.Immutable": "[8.0.0, )", 140 | "System.Diagnostics.DiagnosticSource": "[8.0.1, )", 141 | "System.Memory": "[4.6.0, )", 142 | "System.Runtime": "[4.3.1, )" 143 | } 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/FantomasOnlineV5.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | Lambda 7 | exe 8 | 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/FormatCode.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineV5.FormatCode 2 | 3 | open Fantomas.FCS.Parse 4 | open Fantomas.Core 5 | open Fantomas.Core.FormatConfig 6 | open FantomasOnline.Shared 7 | open FantomasOnline.Server.Shared.Http 8 | open FantomasTools.Client 9 | 10 | let private mapFantomasOptionsToRecord options = 11 | let newValues = 12 | options 13 | |> Seq.map (function 14 | | BoolOption(_, _, v) -> box v 15 | | IntOption(_, _, v) -> box v 16 | | MultilineFormatterTypeOption(_, _, v) -> 17 | MultilineFormatterType.OfConfigString(v) 18 | |> Option.defaultValue CharacterWidth 19 | |> box 20 | | EndOfLineStyleOption(_, _, v) -> 21 | EndOfLineStyle.OfConfigString(v) 22 | |> Option.defaultValue EndOfLineStyle.CRLF 23 | |> box 24 | | MultilineBracketStyleOption(_, _, v) -> 25 | MultilineBracketStyle.OfConfigString(v) 26 | |> Option.defaultValue MultilineBracketStyle.Cramped 27 | |> box) 28 | |> Seq.toArray 29 | 30 | let formatConfigType = typeof 31 | Microsoft.FSharp.Reflection.FSharpValue.MakeRecord(formatConfigType, newValues) :?> FormatConfig.FormatConfig 32 | 33 | let private format (fileName: string) code config = 34 | let isSignature = fileName.EndsWith(".fsi") 35 | CodeFormatter.FormatDocumentAsync(isSignature, code, config) 36 | 37 | let private validate (fileName: string) code = 38 | async { 39 | let isSignature = fileName.EndsWith(".fsi") 40 | 41 | let _tree, diagnostics = 42 | parseFile isSignature (FSharp.Compiler.Text.SourceText.ofString code) [] 43 | 44 | return 45 | diagnostics 46 | |> List.map (fun (e: FSharpParserDiagnostic) -> 47 | let orZero f = Option.map f e.Range |> Option.defaultValue 0 48 | 49 | { SubCategory = e.SubCategory 50 | Range = 51 | { StartLine = orZero (fun r -> r.StartLine) 52 | StartColumn = orZero (fun r -> r.StartColumn) 53 | EndLine = orZero (fun r -> r.EndLine) 54 | EndColumn = orZero (fun r -> r.EndColumn) } 55 | Severity = $"{e.Severity}".ToLower() 56 | ErrorNumber = Option.defaultValue 0 e.ErrorNumber 57 | Message = e.Message } 58 | : Diagnostic) 59 | } 60 | 61 | let getVersion () = 62 | let assembly = typeof.Assembly 63 | 64 | let version = assembly.GetName().Version 65 | sprintf "%i.%i.%i" version.Major version.Minor version.Build 66 | 67 | let getOptions () : string = 68 | Reflection.getRecordFields FormatConfig.FormatConfig.Default 69 | |> Seq.indexed 70 | |> Seq.choose (fun (idx, (k: string, v: obj)) -> 71 | match v with 72 | | :? int as i -> FantomasOption.IntOption(idx, k, i) |> Some 73 | | :? bool as b -> FantomasOption.BoolOption(idx, k, b) |> Some 74 | | :? MultilineFormatterType as mft -> 75 | FantomasOption.MultilineFormatterTypeOption(idx, k, (MultilineFormatterType.ToConfigString mft)) 76 | |> Some 77 | | :? EndOfLineStyle as eol -> 78 | FantomasOption.EndOfLineStyleOption(idx, k, (EndOfLineStyle.ToConfigString eol)) 79 | |> Some 80 | | :? MultilineBracketStyle as mbs -> 81 | FantomasOption.MultilineBracketStyleOption(idx, k, (MultilineBracketStyle.ToConfigString mbs)) 82 | |> Some 83 | | _ -> None) 84 | |> Seq.toList 85 | |> mapOptionsToJson 86 | 87 | let formatCode: string -> Async = 88 | formatCode mapFantomasOptionsToRecord format validate 89 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/Lambda.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineV5.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open HttpConstants 8 | open FantomasOnline.Server.Shared.Http 9 | open FantomasOnlineV5.FormatCode 10 | 11 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 12 | [)>] 13 | () 14 | 15 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, getVersion ()) 17 | 18 | let GetOptions (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 19 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.ApplicationJson, getOptions ()) 20 | 21 | let PostFormat (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 22 | async { 23 | let! response = formatCode request.Body 24 | return mapFormatResponseToAPIGatewayProxyResponse response 25 | } 26 | |> Async.StartAsTask 27 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open Suave.RequestErrors 6 | open SuaveExtensions 7 | open FantomasOnline.Server.Shared.Http 8 | 9 | [] 10 | let main argv = 11 | let mapFormatResponseToWebPart (response: FormatResponse) : WebPart = 12 | match response with 13 | | FormatResponse.Ok body -> (applicationJson >=> OK body) 14 | | FormatResponse.BadRequest error -> (applicationText >=> BAD_REQUEST error) 15 | | FormatResponse.InternalError error -> (applicationText >=> INTERNAL_SERVER_ERROR error) 16 | 17 | let formatWebPart = 18 | request (fun req ctx -> 19 | async { 20 | let json = req.BodyText 21 | let! formatResponse = FantomasOnlineV5.FormatCode.formatCode json 22 | return! (mapFormatResponseToWebPart formatResponse) ctx 23 | }) 24 | 25 | let routes = 26 | [ GET >=> path "/fantomas/v5/version" >=> textPlain >=> OK(FantomasOnlineV5.FormatCode.getVersion ()) 27 | GET >=> path "/fantomas/v5/options" >=> applicationJson >=> OK(FantomasOnlineV5.FormatCode.getOptions ()) 28 | POST >=> path "/fantomas/v5/format" >=> formatWebPart ] 29 | 30 | let port = 31 | match List.ofArray argv with 32 | | [ "--port"; port ] -> System.UInt16.Parse port 33 | | _ -> 11009us 34 | 35 | startFantomasTool port routes 36 | 37 | 0 38 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlineV5/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net8.0": { 5 | "Amazon.Lambda.APIGatewayEvents": { 6 | "type": "Direct", 7 | "requested": "[2.5.0, )", 8 | "resolved": "2.5.0", 9 | "contentHash": "u2M1e8e+eahgwSpa2jhBaakH37EgIZcHqmqpK/9DD/PMygxK5g7LPyUl6SRFnVnmVyD0zvjEh8lYnpYULY6WIQ==" 10 | }, 11 | "Amazon.Lambda.Core": { 12 | "type": "Direct", 13 | "requested": "[2.1.0, )", 14 | "resolved": "2.1.0", 15 | "contentHash": "ok06UhfBZw6j6+PhiJR9C0EOMuJvnq8rMCHVkaFmPFrlI/q447ukwGZQKaAKqodV+MNTfpb/iPxjgUPVbSlVVw==" 16 | }, 17 | "Amazon.Lambda.Serialization.SystemTextJson": { 18 | "type": "Direct", 19 | "requested": "[2.3.0, )", 20 | "resolved": "2.3.0", 21 | "contentHash": "qgFCDJp5lyUNqCq1z18U7fZ/+rmMyw6RJf9nKfnJrf79YupDj02klQAjxymEYN66NykWXyc68SGkow6fy53hfg==", 22 | "dependencies": { 23 | "Amazon.Lambda.Core": "2.1.0" 24 | } 25 | }, 26 | "Fantomas.Core": { 27 | "type": "Direct", 28 | "requested": "[5.2.4, )", 29 | "resolved": "5.2.4", 30 | "contentHash": "Siwn74SoJCIJ8Vqu8IqXYp0rJ2XqZnzvFQJpgoKWCDCnhCTyBPtC5BUSFEGAqqqPO/TZCRx/TnAet+edXwC4HQ==", 31 | "dependencies": { 32 | "FSharp.Core": "6.0.1", 33 | "Fantomas.FCS": "5.2.4" 34 | } 35 | }, 36 | "FSharp.Core": { 37 | "type": "Direct", 38 | "requested": "[8.0.403, )", 39 | "resolved": "8.0.403", 40 | "contentHash": "t1Pvv2++3zMQKKNuiQc1Lz4TCdaBajgG4mLhOE8AoFzborHQ/JbjIaJr6Mrq8m2z15KLu4r6Qz7E3oeACpljTg==" 41 | }, 42 | "Microsoft.Net.Http.Headers": { 43 | "type": "Direct", 44 | "requested": "[2.2.8, )", 45 | "resolved": "2.2.8", 46 | "contentHash": "wHdwMv0QDDG2NWDSwax9cjkeQceGC1Qq53a31+31XpvTXVljKXRjWISlMoS/wZYKiqdqzuEvKFKwGHl+mt2jCA==", 47 | "dependencies": { 48 | "Microsoft.Extensions.Primitives": "2.2.0", 49 | "System.Buffers": "4.5.0" 50 | } 51 | }, 52 | "Suave": { 53 | "type": "Direct", 54 | "requested": "[2.6.2, )", 55 | "resolved": "2.6.2", 56 | "contentHash": "JNTsgb3FrFnvsp7G93Y9XLIGVa47fG4GZ8un/+/iMMVBTRWl8l6Rlnqjo/PsQP6NojtkrLEM4SLz9AhfQIPNag==", 57 | "dependencies": { 58 | "FSharp.Core": "0.0.0" 59 | } 60 | }, 61 | "Thoth.Json.Net": { 62 | "type": "Direct", 63 | "requested": "[8.0.0, )", 64 | "resolved": "8.0.0", 65 | "contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==", 66 | "dependencies": { 67 | "FSharp.Core": "4.7.2", 68 | "Fable.Core": "[3.0.0, 4.0.0)", 69 | "Newtonsoft.Json": "11.0.2" 70 | } 71 | }, 72 | "Fable.Core": { 73 | "type": "Transitive", 74 | "resolved": "3.0.0", 75 | "contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==", 76 | "dependencies": { 77 | "FSharp.Core": "4.5.2" 78 | } 79 | }, 80 | "Fantomas.FCS": { 81 | "type": "Transitive", 82 | "resolved": "5.2.4", 83 | "contentHash": "jqGhrUVHry4lU53vmrG1qTqv83tsyM8uZgYsFEc6tBhl5pSyQnrZCoiCRO19r28I6jPy0m6koabJLUXpJYZy2A==", 84 | "dependencies": { 85 | "FSharp.Core": "6.0.1", 86 | "System.Diagnostics.DiagnosticSource": "7.0.0", 87 | "System.Memory": "4.5.5", 88 | "System.Runtime": "4.3.1" 89 | } 90 | }, 91 | "Microsoft.Extensions.Primitives": { 92 | "type": "Transitive", 93 | "resolved": "2.2.0", 94 | "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", 95 | "dependencies": { 96 | "System.Memory": "4.5.1", 97 | "System.Runtime.CompilerServices.Unsafe": "4.5.1" 98 | } 99 | }, 100 | "Microsoft.NETCore.Platforms": { 101 | "type": "Transitive", 102 | "resolved": "1.1.1", 103 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 104 | }, 105 | "Microsoft.NETCore.Targets": { 106 | "type": "Transitive", 107 | "resolved": "1.1.3", 108 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 109 | }, 110 | "Newtonsoft.Json": { 111 | "type": "Transitive", 112 | "resolved": "11.0.2", 113 | "contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==" 114 | }, 115 | "System.Buffers": { 116 | "type": "Transitive", 117 | "resolved": "4.5.0", 118 | "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" 119 | }, 120 | "System.Diagnostics.DiagnosticSource": { 121 | "type": "Transitive", 122 | "resolved": "7.0.0", 123 | "contentHash": "9W0ewWDuAyDqS2PigdTxk6jDKonfgscY/hP8hm7VpxYhNHZHKvZTdRckberlFk3VnCmr3xBUyMBut12Q+T2aOw==" 124 | }, 125 | "System.Memory": { 126 | "type": "Transitive", 127 | "resolved": "4.5.5", 128 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" 129 | }, 130 | "System.Runtime": { 131 | "type": "Transitive", 132 | "resolved": "4.3.1", 133 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 134 | "dependencies": { 135 | "Microsoft.NETCore.Platforms": "1.1.1", 136 | "Microsoft.NETCore.Targets": "1.1.3" 137 | } 138 | }, 139 | "System.Runtime.CompilerServices.Unsafe": { 140 | "type": "Transitive", 141 | "resolved": "4.5.1", 142 | "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" 143 | } 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/FantomasOnlineV6.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | Lambda 7 | exe 8 | 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/FormatCode.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineV6.FormatCode 2 | 3 | open FantomasTools.Client 4 | open Fantomas.FCS 5 | open Fantomas.FCS.Parse 6 | open Fantomas.Core 7 | open FantomasOnline.Shared 8 | open FantomasOnline.Server.Shared.Http 9 | 10 | let private mapFantomasOptionsToRecord options = 11 | let newValues = 12 | options 13 | |> Seq.map (function 14 | | BoolOption(_, _, v) -> box v 15 | | IntOption(_, _, v) -> box v 16 | | MultilineFormatterTypeOption(_, _, v) -> 17 | MultilineFormatterType.OfConfigString(v) 18 | |> Option.defaultValue CharacterWidth 19 | |> box 20 | | EndOfLineStyleOption(_, _, v) -> 21 | EndOfLineStyle.OfConfigString(v) 22 | |> Option.defaultValue EndOfLineStyle.CRLF 23 | |> box 24 | | MultilineBracketStyleOption(_, _, v) -> 25 | MultilineBracketStyle.OfConfigString(v) 26 | |> Option.defaultValue MultilineBracketStyle.Cramped 27 | |> box) 28 | |> Seq.toArray 29 | 30 | let formatConfigType = typeof 31 | Microsoft.FSharp.Reflection.FSharpValue.MakeRecord(formatConfigType, newValues) :?> FormatConfig 32 | 33 | let private format (fileName: string) code config = 34 | let isSignature = fileName.EndsWith(".fsi") 35 | 36 | async { 37 | let! result = CodeFormatter.FormatDocumentAsync(isSignature, code, config) 38 | return result.Code 39 | } 40 | 41 | let private validate (fileName: string) code = 42 | async { 43 | let isSignature = fileName.EndsWith(".fsi") 44 | 45 | let _tree, diagnostics = parseFile isSignature (Text.SourceText.ofString code) [] 46 | 47 | return 48 | diagnostics 49 | |> List.map (fun (e: FSharpParserDiagnostic) -> 50 | let orZero f = Option.map f e.Range |> Option.defaultValue 0 51 | 52 | { SubCategory = e.SubCategory 53 | Range = 54 | { StartLine = orZero (fun r -> r.StartLine) 55 | StartColumn = orZero (fun r -> r.StartColumn) 56 | EndLine = orZero (fun r -> r.EndLine) 57 | EndColumn = orZero (fun r -> r.EndColumn) } 58 | Severity = $"{e.Severity}".ToLower() 59 | ErrorNumber = Option.defaultValue 0 e.ErrorNumber 60 | Message = e.Message } 61 | : Diagnostic) 62 | } 63 | 64 | let getVersion () = 65 | let assembly = typeof.Assembly 66 | 67 | let version = assembly.GetName().Version 68 | sprintf "%i.%i.%i" version.Major version.Minor version.Build 69 | 70 | let getOptions () : string = 71 | Reflection.getRecordFields FormatConfig.Default 72 | |> Seq.indexed 73 | |> Seq.choose (fun (idx, (k: string, v: obj)) -> 74 | match v with 75 | | :? int as i -> FantomasOption.IntOption(idx, k, i) |> Some 76 | | :? bool as b -> FantomasOption.BoolOption(idx, k, b) |> Some 77 | | :? MultilineFormatterType as mft -> 78 | FantomasOption.MultilineFormatterTypeOption(idx, k, (MultilineFormatterType.ToConfigString mft)) 79 | |> Some 80 | | :? EndOfLineStyle as eol -> 81 | FantomasOption.EndOfLineStyleOption(idx, k, (EndOfLineStyle.ToConfigString eol)) 82 | |> Some 83 | | :? MultilineBracketStyle as mbs -> 84 | FantomasOption.MultilineBracketStyleOption(idx, k, (MultilineBracketStyle.ToConfigString mbs)) 85 | |> Some 86 | | _ -> None) 87 | |> Seq.toList 88 | |> mapOptionsToJson 89 | 90 | let formatCode: string -> Async = 91 | formatCode mapFantomasOptionsToRecord format validate 92 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/Lambda.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineV6.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open HttpConstants 8 | open FantomasOnline.Server.Shared.Http 9 | open FantomasOnlineV6.FormatCode 10 | 11 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 12 | [)>] 13 | () 14 | 15 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, getVersion ()) 17 | 18 | let GetOptions (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 19 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.ApplicationJson, getOptions ()) 20 | 21 | let PostFormat (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 22 | async { 23 | let! response = formatCode request.Body 24 | return mapFormatResponseToAPIGatewayProxyResponse response 25 | } 26 | |> Async.StartAsTask 27 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open Suave.RequestErrors 6 | open SuaveExtensions 7 | open FantomasOnline.Server.Shared.Http 8 | 9 | [] 10 | let main argv = 11 | let mapFormatResponseToWebPart (response: FormatResponse) : WebPart = 12 | match response with 13 | | FormatResponse.Ok body -> (applicationJson >=> OK body) 14 | | FormatResponse.BadRequest error -> (applicationText >=> BAD_REQUEST error) 15 | | FormatResponse.InternalError error -> (applicationText >=> INTERNAL_SERVER_ERROR error) 16 | 17 | let formatWebPart = 18 | request (fun req ctx -> 19 | async { 20 | let json = req.BodyText 21 | let! formatResponse = FantomasOnlineV6.FormatCode.formatCode json 22 | return! (mapFormatResponseToWebPart formatResponse) ctx 23 | }) 24 | 25 | let routes = 26 | [ GET >=> path "/fantomas/v6/version" >=> textPlain >=> OK(FantomasOnlineV6.FormatCode.getVersion ()) 27 | GET >=> path "/fantomas/v6/options" >=> applicationJson >=> OK(FantomasOnlineV6.FormatCode.getOptions ()) 28 | POST >=> path "/fantomas/v6/format" >=> formatWebPart ] 29 | 30 | let port = 31 | match List.ofArray argv with 32 | | [ "--port"; port ] -> System.UInt16.Parse port 33 | | _ -> 13042us 34 | 35 | startFantomasTool port routes 36 | 37 | 0 38 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlineV6/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net8.0": { 5 | "Amazon.Lambda.APIGatewayEvents": { 6 | "type": "Direct", 7 | "requested": "[2.5.0, )", 8 | "resolved": "2.5.0", 9 | "contentHash": "u2M1e8e+eahgwSpa2jhBaakH37EgIZcHqmqpK/9DD/PMygxK5g7LPyUl6SRFnVnmVyD0zvjEh8lYnpYULY6WIQ==" 10 | }, 11 | "Amazon.Lambda.Core": { 12 | "type": "Direct", 13 | "requested": "[2.1.0, )", 14 | "resolved": "2.1.0", 15 | "contentHash": "ok06UhfBZw6j6+PhiJR9C0EOMuJvnq8rMCHVkaFmPFrlI/q447ukwGZQKaAKqodV+MNTfpb/iPxjgUPVbSlVVw==" 16 | }, 17 | "Amazon.Lambda.Serialization.SystemTextJson": { 18 | "type": "Direct", 19 | "requested": "[2.3.0, )", 20 | "resolved": "2.3.0", 21 | "contentHash": "qgFCDJp5lyUNqCq1z18U7fZ/+rmMyw6RJf9nKfnJrf79YupDj02klQAjxymEYN66NykWXyc68SGkow6fy53hfg==", 22 | "dependencies": { 23 | "Amazon.Lambda.Core": "2.1.0" 24 | } 25 | }, 26 | "Fantomas.Core": { 27 | "type": "Direct", 28 | "requested": "[6.3.16, )", 29 | "resolved": "6.3.16", 30 | "contentHash": "t4x1Y5CKzFe8gXGe8QzrJKsmJszX8E5/o+LTUNCqoA7WX6hUyX69PxL1rGBM1sndEP5ThOVa5f5E6CFR7+Ce+w==", 31 | "dependencies": { 32 | "FSharp.Core": "6.0.1", 33 | "Fantomas.FCS": "6.3.16" 34 | } 35 | }, 36 | "FSharp.Core": { 37 | "type": "Direct", 38 | "requested": "[8.0.403, )", 39 | "resolved": "8.0.403", 40 | "contentHash": "t1Pvv2++3zMQKKNuiQc1Lz4TCdaBajgG4mLhOE8AoFzborHQ/JbjIaJr6Mrq8m2z15KLu4r6Qz7E3oeACpljTg==" 41 | }, 42 | "Microsoft.Net.Http.Headers": { 43 | "type": "Direct", 44 | "requested": "[2.2.8, )", 45 | "resolved": "2.2.8", 46 | "contentHash": "wHdwMv0QDDG2NWDSwax9cjkeQceGC1Qq53a31+31XpvTXVljKXRjWISlMoS/wZYKiqdqzuEvKFKwGHl+mt2jCA==", 47 | "dependencies": { 48 | "Microsoft.Extensions.Primitives": "2.2.0", 49 | "System.Buffers": "4.5.0" 50 | } 51 | }, 52 | "Suave": { 53 | "type": "Direct", 54 | "requested": "[2.6.2, )", 55 | "resolved": "2.6.2", 56 | "contentHash": "JNTsgb3FrFnvsp7G93Y9XLIGVa47fG4GZ8un/+/iMMVBTRWl8l6Rlnqjo/PsQP6NojtkrLEM4SLz9AhfQIPNag==", 57 | "dependencies": { 58 | "FSharp.Core": "0.0.0" 59 | } 60 | }, 61 | "Thoth.Json.Net": { 62 | "type": "Direct", 63 | "requested": "[8.0.0, )", 64 | "resolved": "8.0.0", 65 | "contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==", 66 | "dependencies": { 67 | "FSharp.Core": "4.7.2", 68 | "Fable.Core": "[3.0.0, 4.0.0)", 69 | "Newtonsoft.Json": "11.0.2" 70 | } 71 | }, 72 | "Fable.Core": { 73 | "type": "Transitive", 74 | "resolved": "3.0.0", 75 | "contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==", 76 | "dependencies": { 77 | "FSharp.Core": "4.5.2" 78 | } 79 | }, 80 | "Fantomas.FCS": { 81 | "type": "Transitive", 82 | "resolved": "6.3.16", 83 | "contentHash": "rKkiQsqtGZTqxTg4YwXvRWJJ56GTQ//8ceSggMDeBrqgKSuDndZefp3+bLKqSPzMj2++ZW9XGWgoJkYbGIYe+Q==", 84 | "dependencies": { 85 | "FSharp.Core": "6.0.1", 86 | "System.Collections.Immutable": "7.0.0", 87 | "System.Diagnostics.DiagnosticSource": "7.0.0", 88 | "System.Memory": "4.5.5", 89 | "System.Runtime": "4.3.1" 90 | } 91 | }, 92 | "Microsoft.Extensions.Primitives": { 93 | "type": "Transitive", 94 | "resolved": "2.2.0", 95 | "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", 96 | "dependencies": { 97 | "System.Memory": "4.5.1", 98 | "System.Runtime.CompilerServices.Unsafe": "4.5.1" 99 | } 100 | }, 101 | "Microsoft.NETCore.Platforms": { 102 | "type": "Transitive", 103 | "resolved": "1.1.1", 104 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 105 | }, 106 | "Microsoft.NETCore.Targets": { 107 | "type": "Transitive", 108 | "resolved": "1.1.3", 109 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 110 | }, 111 | "Newtonsoft.Json": { 112 | "type": "Transitive", 113 | "resolved": "11.0.2", 114 | "contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==" 115 | }, 116 | "System.Buffers": { 117 | "type": "Transitive", 118 | "resolved": "4.5.0", 119 | "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" 120 | }, 121 | "System.Collections.Immutable": { 122 | "type": "Transitive", 123 | "resolved": "7.0.0", 124 | "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==" 125 | }, 126 | "System.Diagnostics.DiagnosticSource": { 127 | "type": "Transitive", 128 | "resolved": "7.0.0", 129 | "contentHash": "9W0ewWDuAyDqS2PigdTxk6jDKonfgscY/hP8hm7VpxYhNHZHKvZTdRckberlFk3VnCmr3xBUyMBut12Q+T2aOw==" 130 | }, 131 | "System.Memory": { 132 | "type": "Transitive", 133 | "resolved": "4.5.5", 134 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" 135 | }, 136 | "System.Runtime": { 137 | "type": "Transitive", 138 | "resolved": "4.3.1", 139 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 140 | "dependencies": { 141 | "Microsoft.NETCore.Platforms": "1.1.1", 142 | "Microsoft.NETCore.Targets": "1.1.3" 143 | } 144 | }, 145 | "System.Runtime.CompilerServices.Unsafe": { 146 | "type": "Transitive", 147 | "resolved": "4.5.1", 148 | "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" 149 | } 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /src/server/FantomasOnlineV7/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /src/server/FantomasOnlineV7/FantomasOnlineV7.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | Lambda 7 | exe 8 | 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV7/FormatCode.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineV7.FormatCode 2 | 3 | open FantomasTools.Client 4 | open Fantomas.FCS 5 | open Fantomas.FCS.Parse 6 | open Fantomas.Core 7 | open FantomasOnline.Shared 8 | open FantomasOnline.Server.Shared.Http 9 | 10 | let private mapFantomasOptionsToRecord options = 11 | let newValues = 12 | options 13 | |> Seq.map (function 14 | | BoolOption(_, _, v) -> box v 15 | | IntOption(_, _, v) -> box v 16 | | MultilineFormatterTypeOption(_, _, v) -> 17 | MultilineFormatterType.OfConfigString(v) 18 | |> Option.defaultValue CharacterWidth 19 | |> box 20 | | EndOfLineStyleOption(_, _, v) -> 21 | EndOfLineStyle.OfConfigString(v) 22 | |> Option.defaultValue EndOfLineStyle.CRLF 23 | |> box 24 | | MultilineBracketStyleOption(_, _, v) -> 25 | MultilineBracketStyle.OfConfigString(v) 26 | |> Option.defaultValue MultilineBracketStyle.Cramped 27 | |> box) 28 | |> Seq.toArray 29 | 30 | let formatConfigType = typeof 31 | Microsoft.FSharp.Reflection.FSharpValue.MakeRecord(formatConfigType, newValues) :?> FormatConfig 32 | 33 | let private format (fileName: string) code config = 34 | let isSignature = fileName.EndsWith(".fsi") 35 | 36 | async { 37 | let! result = CodeFormatter.FormatDocumentAsync(isSignature, code, config) 38 | return result.Code 39 | } 40 | 41 | let private validate (fileName: string) code = 42 | async { 43 | let isSignature = fileName.EndsWith(".fsi") 44 | 45 | let _tree, diagnostics = parseFile isSignature (Text.SourceText.ofString code) [] 46 | 47 | return 48 | diagnostics 49 | |> List.map (fun (e: FSharpParserDiagnostic) -> 50 | let orZero f = Option.map f e.Range |> Option.defaultValue 0 51 | 52 | { SubCategory = e.SubCategory 53 | Range = 54 | { StartLine = orZero (fun r -> r.StartLine) 55 | StartColumn = orZero (fun r -> r.StartColumn) 56 | EndLine = orZero (fun r -> r.EndLine) 57 | EndColumn = orZero (fun r -> r.EndColumn) } 58 | Severity = $"{e.Severity}".ToLower() 59 | ErrorNumber = Option.defaultValue 0 e.ErrorNumber 60 | Message = e.Message } 61 | : Diagnostic) 62 | } 63 | 64 | let getVersion () = 65 | let assembly = typeof.Assembly 66 | 67 | let version = assembly.GetName().Version 68 | sprintf "%i.%i.%i" version.Major version.Minor version.Build 69 | 70 | let getOptions () : string = 71 | Reflection.getRecordFields FormatConfig.Default 72 | |> Seq.indexed 73 | |> Seq.choose (fun (idx, (k: string, v: obj)) -> 74 | match v with 75 | | :? int as i -> FantomasOption.IntOption(idx, k, i) |> Some 76 | | :? bool as b -> FantomasOption.BoolOption(idx, k, b) |> Some 77 | | :? MultilineFormatterType as mft -> 78 | FantomasOption.MultilineFormatterTypeOption(idx, k, (MultilineFormatterType.ToConfigString mft)) 79 | |> Some 80 | | :? EndOfLineStyle as eol -> 81 | FantomasOption.EndOfLineStyleOption(idx, k, (EndOfLineStyle.ToConfigString eol)) 82 | |> Some 83 | | :? MultilineBracketStyle as mbs -> 84 | FantomasOption.MultilineBracketStyleOption(idx, k, (MultilineBracketStyle.ToConfigString mbs)) 85 | |> Some 86 | | _ -> None) 87 | |> Seq.toList 88 | |> mapOptionsToJson 89 | 90 | let formatCode: string -> Async = 91 | formatCode mapFantomasOptionsToRecord format validate 92 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV7/Lambda.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnlineV7.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open HttpConstants 8 | open FantomasOnline.Server.Shared.Http 9 | open FantomasOnlineV7.FormatCode 10 | 11 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 12 | [)>] 13 | () 14 | 15 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, getVersion ()) 17 | 18 | let GetOptions (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 19 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.ApplicationJson, getOptions ()) 20 | 21 | let PostFormat (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 22 | async { 23 | let! response = formatCode request.Body 24 | return mapFormatResponseToAPIGatewayProxyResponse response 25 | } 26 | |> Async.StartAsTask 27 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV7/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open Suave.RequestErrors 6 | open SuaveExtensions 7 | open FantomasOnline.Server.Shared.Http 8 | 9 | [] 10 | let main argv = 11 | let mapFormatResponseToWebPart (response: FormatResponse) : WebPart = 12 | match response with 13 | | FormatResponse.Ok body -> (applicationJson >=> OK body) 14 | | FormatResponse.BadRequest error -> (applicationText >=> BAD_REQUEST error) 15 | | FormatResponse.InternalError error -> (applicationText >=> INTERNAL_SERVER_ERROR error) 16 | 17 | let formatWebPart = 18 | request (fun req ctx -> 19 | async { 20 | let json = req.BodyText 21 | let! formatResponse = FantomasOnlineV7.FormatCode.formatCode json 22 | return! (mapFormatResponseToWebPart formatResponse) ctx 23 | }) 24 | 25 | let routes = 26 | [ GET >=> path "/fantomas/v7/version" >=> textPlain >=> OK(FantomasOnlineV7.FormatCode.getVersion ()) 27 | GET >=> path "/fantomas/v7/options" >=> applicationJson >=> OK(FantomasOnlineV7.FormatCode.getOptions ()) 28 | POST >=> path "/fantomas/v7/format" >=> formatWebPart ] 29 | 30 | let port = 31 | match List.ofArray argv with 32 | | [ "--port"; port ] -> System.UInt16.Parse port 33 | | _ -> 10707us 34 | 35 | startFantomasTool port routes 36 | 37 | 0 38 | -------------------------------------------------------------------------------- /src/server/FantomasOnlineV7/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/server/HttpConstants.fs: -------------------------------------------------------------------------------- 1 | module HttpConstants 2 | 3 | [] 4 | module HeaderValues = 5 | [] 6 | let ApplicationText = "application/text" 7 | 8 | [] 9 | let TextPlain = "text/plain" 10 | 11 | [] 12 | let ApplicationJson = "application/json" 13 | -------------------------------------------------------------------------------- /src/server/OakViewer/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/server/OakViewer/Decoders.fs: -------------------------------------------------------------------------------- 1 | module OakViewer.Server.Decoders 2 | 3 | open Thoth.Json.Net 4 | open OakViewer 5 | 6 | let private parseRequestDecoder: Decoder = 7 | Decode.object (fun get -> 8 | { SourceCode = get.Required.Field "sourceCode" Decode.string 9 | Defines = get.Required.Field "defines" (Decode.array Decode.string) 10 | IsFsi = get.Required.Field "isFsi" Decode.bool }) 11 | 12 | let decodeParseRequest value = Decode.fromString parseRequestDecoder value 13 | -------------------------------------------------------------------------------- /src/server/OakViewer/Encoders.fs: -------------------------------------------------------------------------------- 1 | module internal OakViewer.Encoders 2 | 3 | open Thoth.Json.Net 4 | open Fantomas.FCS.Diagnostics 5 | open Fantomas.FCS.Text 6 | open Fantomas.FCS.Parse 7 | open Fantomas.Core 8 | open Fantomas.Core.SyntaxOak 9 | 10 | let encodeRange (m: range) = 11 | Encode.object 12 | [ "startLine", Encode.int m.StartLine 13 | "startColumn", Encode.int m.StartColumn 14 | "endLine", Encode.int m.EndLine 15 | "endColumn", Encode.int m.EndColumn ] 16 | 17 | let encodeTriviaNode (triviaNode: TriviaNode) : JsonValue = 18 | let contentType, content = 19 | match triviaNode.Content with 20 | | CommentOnSingleLine comment -> "commentOnSingleLine", Some comment 21 | | LineCommentAfterSourceCode comment -> "lineCommentAfterSourceCode", Some comment 22 | | BlockComment(comment, _, _) -> "blockComment", Some comment 23 | | Newline -> "newline", None 24 | | Directive directive -> "directive", Some directive 25 | | Cursor -> "cursor", None 26 | 27 | Encode.object 28 | [ "range", encodeRange triviaNode.Range 29 | "type", Encode.string contentType 30 | "content", Encode.option Encode.string content ] 31 | 32 | let rec encodeNode (node: Node) (continuation: JsonValue -> JsonValue) : JsonValue = 33 | let continuations = List.map encodeNode (Array.toList node.Children) 34 | 35 | let text = 36 | match node with 37 | | :? SingleTextNode as stn -> 38 | if stn.Text.Length < 13 then 39 | stn.Text 40 | else 41 | sprintf "%s.." (stn.Text.Substring(0, 10)) 42 | |> Some 43 | | _ -> None 44 | 45 | let finalContinuation (children: JsonValue list) = 46 | Encode.object 47 | [ "type", Encode.string (node.GetType().Name) 48 | "text", Encode.option Encode.string text 49 | "range", encodeRange node.Range 50 | "contentBefore", Encode.seq (Seq.map encodeTriviaNode node.ContentBefore) 51 | "children", Encode.list children 52 | "contentAfter", Encode.seq (Seq.map encodeTriviaNode node.ContentAfter) ] 53 | |> continuation 54 | 55 | Continuation.sequence continuations finalContinuation 56 | 57 | let mkRange (range: Range) : FantomasTools.Client.Range = 58 | { StartLine = range.StartLine 59 | StartColumn = range.StartColumn 60 | EndLine = range.EndLine 61 | EndColumn = range.EndColumn } 62 | 63 | let fsharpErrorInfoSeverity = 64 | function 65 | | FSharpDiagnosticSeverity.Warning -> "warning" 66 | | FSharpDiagnosticSeverity.Error -> "error" 67 | | FSharpDiagnosticSeverity.Hidden -> "hidden" 68 | | FSharpDiagnosticSeverity.Info -> "info" 69 | 70 | let encodeFSharpErrorInfo (info: FSharpParserDiagnostic) = 71 | ({ SubCategory = info.SubCategory 72 | Range = 73 | match info.Range with 74 | | None -> mkRange Range.Zero 75 | | Some r -> mkRange r 76 | Severity = fsharpErrorInfoSeverity info.Severity 77 | ErrorNumber = Option.defaultValue 0 info.ErrorNumber 78 | Message = info.Message } 79 | : FantomasTools.Client.Diagnostic) 80 | |> FantomasTools.Client.Diagnostic.Encode 81 | 82 | let encode (root: Node) (diagnostics: FSharpParserDiagnostic list) = 83 | Encode.object 84 | [ "oak", encodeNode root id 85 | "diagnostics", Encode.list (List.map encodeFSharpErrorInfo diagnostics) ] 86 | -------------------------------------------------------------------------------- /src/server/OakViewer/Encoders.fsi: -------------------------------------------------------------------------------- 1 | module internal OakViewer.Encoders 2 | 3 | open Thoth.Json.Net 4 | open Fantomas.FCS.Parse 5 | open Fantomas.Core.SyntaxOak 6 | 7 | val encode: root: Node -> diagnostics: FSharpParserDiagnostic list -> JsonValue 8 | -------------------------------------------------------------------------------- /src/server/OakViewer/GetOak.fs: -------------------------------------------------------------------------------- 1 | module OakViewer.GetOak 2 | 3 | open Fantomas.Core 4 | open OakViewer.Server 5 | 6 | let getVersion () : string = 7 | let assembly = Fantomas.FCS.Parse.parseFile.GetType().Assembly 8 | let version = assembly.GetName().Version 9 | $"%i{version.Major}.%i{version.Minor}.%i{version.Revision}" 10 | 11 | let private parseAST source defines isFsi = Fantomas.FCS.Parse.parseFile isFsi source defines 12 | 13 | [] 14 | type GetOakResponse = 15 | | Ok of text: string 16 | | BadRequest of body: string 17 | 18 | let getOak json : GetOakResponse = 19 | let parseRequest = Decoders.decodeParseRequest json 20 | 21 | match parseRequest with 22 | | Ok pr -> 23 | let { SourceCode = content 24 | Defines = defines 25 | IsFsi = isFsi } = 26 | pr 27 | 28 | let oakResult = 29 | try 30 | let ast, diagnostics = 31 | Fantomas.FCS.Parse.parseFile 32 | isFsi 33 | (Fantomas.FCS.Text.SourceText.ofString content) 34 | (List.ofArray defines) 35 | 36 | let oak = CodeFormatter.TransformAST(ast, content) 37 | Ok(oak, diagnostics) 38 | with ex -> 39 | Error ex 40 | 41 | match oakResult with 42 | | Error ex -> GetOakResponse.BadRequest(ex.Message) 43 | | Ok(oak, diagnostics) -> 44 | let responseText = 45 | Encoders.encode oak diagnostics |> Thoth.Json.Net.Encode.toString 4 46 | 47 | GetOakResponse.Ok responseText 48 | 49 | | Error err -> GetOakResponse.BadRequest(string err) 50 | -------------------------------------------------------------------------------- /src/server/OakViewer/Lambda.fs: -------------------------------------------------------------------------------- 1 | module OakViewer.Lambda 2 | 3 | open System.Net 4 | open Amazon.Lambda.APIGatewayEvents 5 | open Amazon.Lambda.Core 6 | open AWSLambdaExtensions 7 | open HttpConstants 8 | open OakViewer.GetOak 9 | 10 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 11 | [)>] 12 | () 13 | 14 | let GetVersion (_request: APIGatewayProxyRequest) (_context: ILambdaContext) = 15 | let version = getVersion () 16 | mkAPIGatewayProxyResponse (HttpStatusCode.OK, HeaderValues.TextPlain, version) 17 | 18 | let GetOak (request: APIGatewayProxyRequest) (_context: ILambdaContext) = 19 | let oakResponse = getOak request.Body 20 | 21 | let responseData = 22 | match oakResponse with 23 | | GetOakResponse.Ok body -> HttpStatusCode.OK, HeaderValues.ApplicationText, body 24 | | GetOakResponse.BadRequest body -> HttpStatusCode.BadRequest, HeaderValues.ApplicationText, body 25 | 26 | mkAPIGatewayProxyResponse responseData 27 | -------------------------------------------------------------------------------- /src/server/OakViewer/OakViewer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | Lambda 7 | exe 8 | 9 | true 10 | false 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/server/OakViewer/Program.fs: -------------------------------------------------------------------------------- 1 | open Suave 2 | open Suave.Filters 3 | open Suave.Operators 4 | open Suave.Successful 5 | open Suave.RequestErrors 6 | open SuaveExtensions 7 | open OakViewer.GetOak 8 | 9 | [] 10 | let main argv = 11 | let mapOakResponseToWebPart (response: GetOakResponse) : WebPart = 12 | match response with 13 | | GetOakResponse.Ok body -> (applicationText >=> OK body) 14 | | GetOakResponse.BadRequest errors -> (applicationText >=> BAD_REQUEST errors) 15 | 16 | let getOakWebPart = 17 | request (fun req ctx -> 18 | async { 19 | let json = req.BodyText 20 | let astResponse = getOak json 21 | return! (mapOakResponseToWebPart astResponse) ctx 22 | }) 23 | 24 | let routes = 25 | [ GET >=> path "/oak-viewer/version" >=> textPlain >=> OK(getVersion ()) 26 | POST >=> path "/oak-viewer/get-oak" >=> getOakWebPart ] 27 | 28 | let port = 29 | match List.ofArray argv with 30 | | [ "--port"; port ] -> System.UInt16.Parse port 31 | | _ -> 8904us 32 | 33 | startFantomasTool port routes 34 | 35 | 0 36 | -------------------------------------------------------------------------------- /src/server/OakViewer/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/server/OakViewer/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net8.0": { 5 | "Amazon.Lambda.APIGatewayEvents": { 6 | "type": "Direct", 7 | "requested": "[2.5.0, )", 8 | "resolved": "2.5.0", 9 | "contentHash": "u2M1e8e+eahgwSpa2jhBaakH37EgIZcHqmqpK/9DD/PMygxK5g7LPyUl6SRFnVnmVyD0zvjEh8lYnpYULY6WIQ==" 10 | }, 11 | "Amazon.Lambda.Core": { 12 | "type": "Direct", 13 | "requested": "[2.1.0, )", 14 | "resolved": "2.1.0", 15 | "contentHash": "ok06UhfBZw6j6+PhiJR9C0EOMuJvnq8rMCHVkaFmPFrlI/q447ukwGZQKaAKqodV+MNTfpb/iPxjgUPVbSlVVw==" 16 | }, 17 | "Amazon.Lambda.Serialization.SystemTextJson": { 18 | "type": "Direct", 19 | "requested": "[2.3.0, )", 20 | "resolved": "2.3.0", 21 | "contentHash": "qgFCDJp5lyUNqCq1z18U7fZ/+rmMyw6RJf9nKfnJrf79YupDj02klQAjxymEYN66NykWXyc68SGkow6fy53hfg==", 22 | "dependencies": { 23 | "Amazon.Lambda.Core": "2.1.0" 24 | } 25 | }, 26 | "FSharp.Core": { 27 | "type": "Direct", 28 | "requested": "[8.0.403, )", 29 | "resolved": "8.0.403", 30 | "contentHash": "t1Pvv2++3zMQKKNuiQc1Lz4TCdaBajgG4mLhOE8AoFzborHQ/JbjIaJr6Mrq8m2z15KLu4r6Qz7E3oeACpljTg==" 31 | }, 32 | "Microsoft.Net.Http.Headers": { 33 | "type": "Direct", 34 | "requested": "[2.2.8, )", 35 | "resolved": "2.2.8", 36 | "contentHash": "wHdwMv0QDDG2NWDSwax9cjkeQceGC1Qq53a31+31XpvTXVljKXRjWISlMoS/wZYKiqdqzuEvKFKwGHl+mt2jCA==", 37 | "dependencies": { 38 | "Microsoft.Extensions.Primitives": "2.2.0", 39 | "System.Buffers": "4.5.0" 40 | } 41 | }, 42 | "Suave": { 43 | "type": "Direct", 44 | "requested": "[2.6.2, )", 45 | "resolved": "2.6.2", 46 | "contentHash": "JNTsgb3FrFnvsp7G93Y9XLIGVa47fG4GZ8un/+/iMMVBTRWl8l6Rlnqjo/PsQP6NojtkrLEM4SLz9AhfQIPNag==", 47 | "dependencies": { 48 | "FSharp.Core": "0.0.0" 49 | } 50 | }, 51 | "Thoth.Json.Net": { 52 | "type": "Direct", 53 | "requested": "[8.0.0, )", 54 | "resolved": "8.0.0", 55 | "contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==", 56 | "dependencies": { 57 | "FSharp.Core": "4.7.2", 58 | "Fable.Core": "[3.0.0, 4.0.0)", 59 | "Newtonsoft.Json": "11.0.2" 60 | } 61 | }, 62 | "Fable.Core": { 63 | "type": "Transitive", 64 | "resolved": "3.0.0", 65 | "contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==", 66 | "dependencies": { 67 | "FSharp.Core": "4.5.2" 68 | } 69 | }, 70 | "Microsoft.Extensions.Primitives": { 71 | "type": "Transitive", 72 | "resolved": "2.2.0", 73 | "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", 74 | "dependencies": { 75 | "System.Memory": "4.5.1", 76 | "System.Runtime.CompilerServices.Unsafe": "4.5.1" 77 | } 78 | }, 79 | "Microsoft.NETCore.Platforms": { 80 | "type": "Transitive", 81 | "resolved": "1.1.1", 82 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 83 | }, 84 | "Microsoft.NETCore.Targets": { 85 | "type": "Transitive", 86 | "resolved": "1.1.3", 87 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 88 | }, 89 | "Newtonsoft.Json": { 90 | "type": "Transitive", 91 | "resolved": "11.0.2", 92 | "contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==" 93 | }, 94 | "System.Buffers": { 95 | "type": "Transitive", 96 | "resolved": "4.5.0", 97 | "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" 98 | }, 99 | "System.Collections.Immutable": { 100 | "type": "Transitive", 101 | "resolved": "8.0.0", 102 | "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" 103 | }, 104 | "System.Diagnostics.DiagnosticSource": { 105 | "type": "Transitive", 106 | "resolved": "8.0.1", 107 | "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" 108 | }, 109 | "System.Memory": { 110 | "type": "Transitive", 111 | "resolved": "4.6.0", 112 | "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==" 113 | }, 114 | "System.Runtime": { 115 | "type": "Transitive", 116 | "resolved": "4.3.1", 117 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 118 | "dependencies": { 119 | "Microsoft.NETCore.Platforms": "1.1.1", 120 | "Microsoft.NETCore.Targets": "1.1.3" 121 | } 122 | }, 123 | "System.Runtime.CompilerServices.Unsafe": { 124 | "type": "Transitive", 125 | "resolved": "4.5.1", 126 | "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" 127 | }, 128 | "fantomas.core": { 129 | "type": "Project", 130 | "dependencies": { 131 | "FSharp.Core": "[8.0.100, )", 132 | "Fantomas.FCS": "[1.0.0, )" 133 | } 134 | }, 135 | "fantomas.fcs": { 136 | "type": "Project", 137 | "dependencies": { 138 | "FSharp.Core": "[8.0.100, )", 139 | "System.Collections.Immutable": "[8.0.0, )", 140 | "System.Diagnostics.DiagnosticSource": "[8.0.1, )", 141 | "System.Memory": "[4.6.0, )", 142 | "System.Runtime": "[4.3.1, )" 143 | } 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/server/SuaveExtensions.fs: -------------------------------------------------------------------------------- 1 | module SuaveExtensions 2 | 3 | open System.Net 4 | open System.Net.Sockets 5 | open Suave 6 | open Suave.Operators 7 | open Suave.Writers 8 | open Suave.Response 9 | open Suave.Filters 10 | open Suave.Successful 11 | open Suave.RequestErrors 12 | open HttpConstants 13 | 14 | type HttpRequest with 15 | 16 | member this.BodyText = System.Text.Encoding.UTF8.GetString this.rawForm 17 | 18 | let applicationJson = setMimeType HeaderValues.ApplicationJson 19 | let applicationText = setMimeType HeaderValues.ApplicationText 20 | let textPlain = setMimeType HeaderValues.TextPlain 21 | let private getBytes (v: string) = System.Text.Encoding.UTF8.GetBytes v 22 | let REQUEST_ENTITY_TOO_LARGE (body: string) = response HTTP_413 (getBytes body) 23 | let INTERNAL_SERVER_ERROR (body: string) = response HTTP_500 (getBytes body) 24 | 25 | let setCORSHeaders = 26 | addHeader "Access-Control-Allow-Origin" "*" 27 | >=> addHeader "Access-Control-Allow-Headers" "*" 28 | >=> addHeader "Access-Control-Allow-Methods" "*" 29 | 30 | let startFantomasTool port routes = 31 | try 32 | setCORSHeaders 33 | >=> choose [ OPTIONS >=> no_content; yield! routes; NOT_FOUND "Not found" ] 34 | |> startWebServer 35 | { defaultConfig with 36 | bindings = [ HttpBinding.create HTTP IPAddress.Loopback port ] } 37 | with :? SocketException -> 38 | printfn $"Port {port} is already in use" 39 | -------------------------------------------------------------------------------- /src/shared/ASTViewerShared.fs: -------------------------------------------------------------------------------- 1 | module ASTViewer.Shared 2 | 3 | open FantomasTools.Client 4 | 5 | type Response = 6 | { Ast: string 7 | Diagnostics: Diagnostic array } 8 | 9 | type Request = 10 | { SourceCode: string 11 | Defines: string array 12 | IsFsi: bool 13 | Expand: bool } 14 | -------------------------------------------------------------------------------- /src/shared/FantomasOnlineShared.fs: -------------------------------------------------------------------------------- 1 | module FantomasOnline.Shared 2 | 3 | open FantomasTools.Client 4 | 5 | type FantomasOption = 6 | | IntOption of order: int * name: string * value: int 7 | | BoolOption of order: int * name: string * value: bool 8 | | MultilineFormatterTypeOption of order: int * name: string * value: string 9 | | EndOfLineStyleOption of order: int * name: string * value: string 10 | | MultilineBracketStyleOption of order: int * name: string * value: string 11 | 12 | let sortByOption = 13 | function 14 | | IntOption(o, _, _) 15 | | BoolOption(o, _, _) 16 | | MultilineFormatterTypeOption(o, _, _) 17 | | EndOfLineStyleOption(o, _, _) 18 | | MultilineBracketStyleOption(o, _, _) -> o 19 | 20 | let getOptionKey = 21 | function 22 | | IntOption(_, k, _) 23 | | BoolOption(_, k, _) 24 | | MultilineFormatterTypeOption(_, k, _) 25 | | EndOfLineStyleOption(_, k, _) 26 | | MultilineBracketStyleOption(_, k, _) -> k 27 | 28 | let optionValue = 29 | function 30 | | IntOption(_, _, i) -> i.ToString() 31 | | BoolOption(_, _, b) -> b.ToString() 32 | | MultilineFormatterTypeOption(_, _, v) 33 | | EndOfLineStyleOption(_, _, v) 34 | | MultilineBracketStyleOption(_, _, v) -> v 35 | 36 | let tryGetUserOptionValue userOptions key castFunc = 37 | userOptions |> Map.tryFind key |> Option.map (optionValue >> castFunc) 38 | 39 | let tryGetDefaultOptionValue defaultOptions key castFunc = 40 | defaultOptions 41 | |> List.tryFind (fun o -> (getOptionKey o) = key) 42 | |> Option.map (optionValue >> castFunc) 43 | 44 | let tryGetOptionValue userOptions defaultOptions key castFunc = 45 | let userOption = tryGetUserOptionValue userOptions key castFunc 46 | 47 | match userOption with 48 | | Some n -> Some n 49 | | None -> tryGetDefaultOptionValue defaultOptions key castFunc 50 | 51 | type FormatRequest = 52 | { SourceCode: string 53 | Options: FantomasOption list 54 | IsFsi: bool } 55 | 56 | type FormatResponse = 57 | { FirstFormat: string 58 | FirstValidation: Diagnostic array 59 | SecondFormat: string option 60 | SecondValidation: Diagnostic array } 61 | 62 | let private supportedProperties = 63 | set [| "max_line_length"; "indent_size"; "end_of_line" |] 64 | 65 | let toEditorConfigName value = 66 | value 67 | |> Seq.map (fun c -> 68 | if System.Char.IsUpper(c) then 69 | sprintf "_%s" (c.ToString().ToLower()) 70 | else 71 | c.ToString()) 72 | |> String.concat "" 73 | |> fun s -> s.TrimStart([| '_' |]) 74 | |> fun name -> 75 | if Set.contains name supportedProperties then 76 | name 77 | else 78 | sprintf "fsharp_%s" name 79 | -------------------------------------------------------------------------------- /src/shared/OakShared.fs: -------------------------------------------------------------------------------- 1 | namespace OakViewer 2 | 3 | type ParseRequest = 4 | { SourceCode: string 5 | Defines: string array 6 | IsFsi: bool } 7 | -------------------------------------------------------------------------------- /src/shared/Types.fs: -------------------------------------------------------------------------------- 1 | namespace FantomasTools.Client 2 | 3 | #if FABLE_COMPILER 4 | open Thoth.Json 5 | #else 6 | open Thoth.Json.Net 7 | #endif 8 | 9 | type Range = 10 | { StartLine: int 11 | StartColumn: int 12 | EndLine: int 13 | EndColumn: int } 14 | 15 | static member Zero = 16 | { StartLine = 0 17 | StartColumn = 0 18 | EndLine = 0 19 | EndColumn = 0 } 20 | 21 | #if FABLE_COMPILER 22 | static member Decode: Decoder = 23 | Decode.object (fun get -> 24 | { StartLine = get.Required.Field "startLine" Decode.int 25 | StartColumn = get.Required.Field "startColumn" Decode.int 26 | EndLine = get.Required.Field "endLine" Decode.int 27 | EndColumn = get.Required.Field "endColumn" Decode.int }) 28 | #else 29 | static member Encode(range: Range) : JsonValue = 30 | Encode.object 31 | [ "startLine", Encode.int range.StartLine 32 | "startColumn", Encode.int range.StartColumn 33 | "endLine", Encode.int range.EndLine 34 | "endColumn", Encode.int range.EndColumn ] 35 | #endif 36 | 37 | type Diagnostic = 38 | { SubCategory: string 39 | Range: Range 40 | Severity: string 41 | ErrorNumber: int 42 | Message: string } 43 | 44 | #if FABLE_COMPILER 45 | static member Decode: Decoder = 46 | Decode.object (fun get -> 47 | { SubCategory = get.Required.Field "subcategory" Decode.string 48 | Range = get.Required.Field "range" Range.Decode 49 | Severity = get.Required.Field "severity" Decode.string 50 | ErrorNumber = get.Required.Field "errorNumber" Decode.int 51 | Message = get.Required.Field "message" Decode.string }) 52 | #else 53 | static member Encode(diagnostic: Diagnostic) : JsonValue = 54 | Encode.object 55 | [ "subcategory", Encode.string diagnostic.SubCategory 56 | "range", Range.Encode diagnostic.Range 57 | "severity", Encode.string diagnostic.Severity 58 | "errorNumber", Encode.int diagnostic.ErrorNumber 59 | "message", Encode.string diagnostic.Message ] 60 | #endif 61 | --------------------------------------------------------------------------------