├── .dockerignore ├── .github ├── FUNDING.yml ├── renovate.json └── workflows │ ├── codeql-analysis.yml │ ├── deploy.yml │ ├── spm.yml │ └── test.yml ├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── swiftfiddle-lsp.xcscheme ├── .vscode └── launch.json ├── DEPLOYMENT.md ├── Dockerfile ├── LICENSE ├── Package.resolved ├── Package.swift ├── Public ├── css │ └── common.css ├── error.html ├── favicons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── images │ └── ogp_image.png ├── index.html ├── index.js ├── js │ ├── about.js │ ├── configuration.js │ ├── debounce.js │ ├── defaults.js │ ├── editor.js │ ├── icon.js │ ├── result_view.js │ ├── snackbar.js │ └── swift_format.js ├── robots.txt └── scss │ └── default.scss ├── README.md ├── Resources └── formatter │ ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── contents.xcworkspacedata │ ├── Package.resolved │ └── Package.swift ├── SECURITY.md ├── Sources └── App │ ├── Middlewares │ ├── CommonErrorMiddleware.swift │ └── CustomHeaderMiddleware.swift │ ├── configure.swift │ ├── entrypoint.swift │ └── routes.swift ├── fly.toml ├── package-lock.json ├── package.json ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: kishikawakatsumi 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "packageRules": [ 4 | { 5 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 6 | "automerge": true 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '37 13 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v3 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v3 72 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Fly Deploy 2 | on: 3 | push: 4 | branches: [main] 5 | workflow_dispatch: 6 | 7 | env: 8 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 9 | FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOME_TOKEN }} 10 | 11 | jobs: 12 | deploy: 13 | name: Deploy app 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: superfly/flyctl-actions/setup-flyctl@master 18 | - run: flyctl deploy --build-arg FONTAWESOME_TOKEN=${{ env.FONTAWESOME_TOKEN }} --local-only 19 | -------------------------------------------------------------------------------- /.github/workflows/spm.yml: -------------------------------------------------------------------------------- 1 | name: Update Package.resolved 2 | on: 3 | schedule: 4 | - cron: "0 0 * * 1" 5 | workflow_dispatch: 6 | 7 | jobs: 8 | run: 9 | runs-on: macos-14 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Update Package.resolved 13 | run: | 14 | set -ex 15 | swift package update 16 | swift build 17 | git add Package.resolved 18 | git diff-index --quiet HEAD || git commit -m "Update Package.resolved" 19 | git push origin main 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | branches: [main] 5 | workflow_dispatch: 6 | 7 | env: 8 | FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOME_TOKEN }} 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Build 16 | run: | 17 | set -ex 18 | docker build --rm --no-cache --build-arg FONTAWESOME_TOKEN=${{ env.FONTAWESOME_TOKEN }} . 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/991e760c1c6d50fdda246e0178b9c58b06770b90/Global/macOS.gitignore 2 | 3 | # General 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | ### https://raw.github.com/github/gitignore/991e760c1c6d50fdda246e0178b9c58b06770b90/Swift.gitignore 32 | 33 | # Xcode 34 | # 35 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 36 | 37 | ## User settings 38 | xcuserdata/ 39 | 40 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 41 | *.xcscmblueprint 42 | *.xccheckout 43 | 44 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 45 | build/ 46 | DerivedData/ 47 | *.moved-aside 48 | *.pbxuser 49 | !default.pbxuser 50 | *.mode1v3 51 | !default.mode1v3 52 | *.mode2v3 53 | !default.mode2v3 54 | *.perspectivev3 55 | !default.perspectivev3 56 | 57 | ## Obj-C/Swift specific 58 | *.hmap 59 | 60 | ## App packaging 61 | *.ipa 62 | *.dSYM.zip 63 | *.dSYM 64 | 65 | ## Playgrounds 66 | timeline.xctimeline 67 | playground.xcworkspace 68 | 69 | # Swift Package Manager 70 | # 71 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 72 | # Packages/ 73 | # Package.pins 74 | # Package.resolved 75 | # *.xcodeproj 76 | # 77 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 78 | # hence it is not needed unless you have added a package configuration file to your project 79 | # .swiftpm 80 | 81 | .build/ 82 | 83 | # CocoaPods 84 | # 85 | # We recommend against adding the Pods directory to your .gitignore. However 86 | # you should judge for yourself, the pros and cons are mentioned at: 87 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 88 | # 89 | # Pods/ 90 | # 91 | # Add this line if you want to avoid checking in source code from the Xcode workspace 92 | # *.xcworkspace 93 | 94 | # Carthage 95 | # 96 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 97 | # Carthage/Checkouts 98 | 99 | Carthage/Build/ 100 | 101 | # Accio dependency management 102 | Dependencies/ 103 | .accio/ 104 | 105 | # fastlane 106 | # 107 | # It is recommended to not store the screenshots in the git repo. 108 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 109 | # For more information about the recommended setup visit: 110 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 111 | 112 | fastlane/report.xml 113 | fastlane/Preview.html 114 | fastlane/screenshots/**/*.png 115 | fastlane/test_output 116 | 117 | # Code Injection 118 | # 119 | # After new code Injection tools there's a generated folder /iOSInjectionProject 120 | # https://github.com/johnno1962/injectionforxcode 121 | 122 | iOSInjectionProject/ 123 | 124 | 125 | ### https://raw.github.com/github/gitignore/991e760c1c6d50fdda246e0178b9c58b06770b90/Node.gitignore 126 | 127 | # Logs 128 | logs 129 | *.log 130 | npm-debug.log* 131 | yarn-debug.log* 132 | yarn-error.log* 133 | lerna-debug.log* 134 | .pnpm-debug.log* 135 | 136 | # Diagnostic reports (https://nodejs.org/api/report.html) 137 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 138 | 139 | # Runtime data 140 | pids 141 | *.pid 142 | *.seed 143 | *.pid.lock 144 | 145 | # Directory for instrumented libs generated by jscoverage/JSCover 146 | lib-cov 147 | 148 | # Coverage directory used by tools like istanbul 149 | coverage 150 | *.lcov 151 | 152 | # nyc test coverage 153 | .nyc_output 154 | 155 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 156 | .grunt 157 | 158 | # Bower dependency directory (https://bower.io/) 159 | bower_components 160 | 161 | # node-waf configuration 162 | .lock-wscript 163 | 164 | # Compiled binary addons (https://nodejs.org/api/addons.html) 165 | build/Release 166 | 167 | # Dependency directories 168 | node_modules/ 169 | jspm_packages/ 170 | 171 | # Snowpack dependency directory (https://snowpack.dev/) 172 | web_modules/ 173 | 174 | # TypeScript cache 175 | *.tsbuildinfo 176 | 177 | # Optional npm cache directory 178 | .npm 179 | 180 | # Optional eslint cache 181 | .eslintcache 182 | 183 | # Microbundle cache 184 | .rpt2_cache/ 185 | .rts2_cache_cjs/ 186 | .rts2_cache_es/ 187 | .rts2_cache_umd/ 188 | 189 | # Optional REPL history 190 | .node_repl_history 191 | 192 | # Output of 'npm pack' 193 | *.tgz 194 | 195 | # Yarn Integrity file 196 | .yarn-integrity 197 | 198 | # dotenv environment variables file 199 | .env 200 | .env.test 201 | .env.production 202 | 203 | # parcel-bundler cache (https://parceljs.org/) 204 | .cache 205 | .parcel-cache 206 | 207 | # Next.js build output 208 | .next 209 | out 210 | 211 | # Nuxt.js build / generate output 212 | .nuxt 213 | dist 214 | 215 | # Gatsby files 216 | .cache/ 217 | # Comment in the public line in if your project uses Gatsby and not Next.js 218 | # https://nextjs.org/blog/next-9-1#public-directory-support 219 | # public 220 | 221 | # vuepress build output 222 | .vuepress/dist 223 | 224 | # Serverless directories 225 | .serverless/ 226 | 227 | # FuseBox cache 228 | .fusebox/ 229 | 230 | # DynamoDB Local files 231 | .dynamodb/ 232 | 233 | # TernJS port file 234 | .tern-port 235 | 236 | # Stores VSCode versions used for testing VSCode extensions 237 | .vscode-test 238 | 239 | # yarn v2 240 | .yarn/cache 241 | .yarn/unplugged 242 | .yarn/build-state.yml 243 | .yarn/install-state.gz 244 | .pnp.* 245 | 246 | 247 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/swiftfiddle-lsp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 57 | 59 | 65 | 66 | 67 | 68 | 74 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "sourceLanguages": ["swift"], 7 | "name": "Debug App", 8 | "program": "${workspaceFolder:swiftfiddle-formatter}/.build/debug/App", 9 | "args": [], 10 | "cwd": "${workspaceFolder:swiftfiddle-formatter}", 11 | "preLaunchTask": "swift: Build Debug App" 12 | }, 13 | { 14 | "type": "lldb", 15 | "request": "launch", 16 | "sourceLanguages": ["swift"], 17 | "name": "Release App", 18 | "program": "${workspaceFolder:swiftfiddle-formatter}/.build/release/App", 19 | "args": [], 20 | "cwd": "${workspaceFolder:swiftfiddle-formatter}", 21 | "preLaunchTask": "swift: Build Release App" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | # Deployment Instructions 2 | 3 | ## Prerequisites 4 | 5 | Before deploying, make sure you have the following software installed on your machine: 6 | 7 | - Node.js (v14 or newer) 8 | - Docker (v20.10 or newer) 9 | 10 | The following environment variables are used for deployment: 11 | 12 | - `FONTAWESOME_TOKEN`: This token is used for authentication with the FontAwesome service. You need to obtain a valid token from your FontAwesome account and use it here. Please make sure not to expose this token publicly. 13 | 14 | ## Local Deployment 15 | 16 | ### Steps: 17 | 18 | 1. Install the dependencies: 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | 2. Run Webpack to build the project: 25 | 26 | ```bash 27 | npm run prod 28 | ``` 29 | 30 | 3. Run the application: 31 | 32 | ```bash 33 | swift run 34 | ``` 35 | 36 | You should now be able to see the application running at `localhost:8080`. 37 | 38 | ## Production Deployment 39 | 40 | For deploying to production, we recommend using [Fly.io](https://fly.io/). Fly.io is a platform that allows you to deploy your application to the cloud with ease. It also provides a free tier that is sufficient for deploying this application. 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-slim as node 2 | 3 | WORKDIR /build 4 | 5 | ARG FONTAWESOME_TOKEN 6 | COPY package*.json ./ 7 | RUN echo "@fortawesome:registry=https://npm.fontawesome.com/\n//npm.fontawesome.com/:_authToken=${FONTAWESOME_TOKEN}" > ./.npmrc \ 8 | && npm ci \ 9 | && rm -f ./.npmrc 10 | 11 | COPY webpack.*.js ./ 12 | COPY Public ./Public/ 13 | RUN npx webpack --config webpack.prod.js 14 | 15 | 16 | FROM swift:6.1-jammy as swift 17 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 18 | && apt-get -q update \ 19 | && apt-get -q dist-upgrade -y\ 20 | && rm -rf /var/lib/apt/lists/* 21 | 22 | WORKDIR /build 23 | COPY --from=node /build /build 24 | COPY ./Package.* ./ 25 | RUN swift package resolve 26 | 27 | COPY . . 28 | RUN swift build -c release --static-swift-stdlib 29 | 30 | COPY ./Resources ./ 31 | RUN cd Resources/formatter && swift build --product swift-format -c release --static-swift-stdlib 32 | 33 | WORKDIR /staging 34 | 35 | RUN cp "$(swift build --package-path /build -c release --show-bin-path)/App" ./ 36 | 37 | RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \; 38 | 39 | RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true 40 | RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true 41 | 42 | 43 | FROM swift:6.1-jammy-slim 44 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 45 | && apt-get -q update \ 46 | && apt-get -q dist-upgrade -y \ 47 | && apt-get -q install -y \ 48 | ca-certificates \ 49 | tzdata \ 50 | && rm -r /var/lib/apt/lists/* 51 | 52 | RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor 53 | 54 | WORKDIR /app 55 | COPY --from=swift --chown=vapor:vapor /staging /app 56 | 57 | USER vapor:vapor 58 | EXPOSE 8080 59 | 60 | ENTRYPOINT ["./App"] 61 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0"] 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kishikawa Katsumi 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 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "async-http-client", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/swift-server/async-http-client.git", 7 | "state" : { 8 | "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", 9 | "version" : "1.26.1" 10 | } 11 | }, 12 | { 13 | "identity" : "async-kit", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/vapor/async-kit.git", 16 | "state" : { 17 | "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", 18 | "version" : "1.20.0" 19 | } 20 | }, 21 | { 22 | "identity" : "console-kit", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/vapor/console-kit.git", 25 | "state" : { 26 | "revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", 27 | "version" : "4.15.2" 28 | } 29 | }, 30 | { 31 | "identity" : "leaf", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/vapor/leaf.git", 34 | "state" : { 35 | "revision" : "d469584b9186851c5a4012d11325fb9db3207ebb", 36 | "version" : "4.5.0" 37 | } 38 | }, 39 | { 40 | "identity" : "leaf-kit", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/vapor/leaf-kit.git", 43 | "state" : { 44 | "revision" : "cf186d8f2ef33e16fd1dd78df36466c22c2e632f", 45 | "version" : "1.13.1" 46 | } 47 | }, 48 | { 49 | "identity" : "multipart-kit", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/vapor/multipart-kit.git", 52 | "state" : { 53 | "revision" : "3498e60218e6003894ff95192d756e238c01f44e", 54 | "version" : "4.7.1" 55 | } 56 | }, 57 | { 58 | "identity" : "routing-kit", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/vapor/routing-kit.git", 61 | "state" : { 62 | "revision" : "93f7222c8e195cbad39fafb5a0e4cc85a8def7ea", 63 | "version" : "4.9.2" 64 | } 65 | }, 66 | { 67 | "identity" : "swift-algorithms", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/apple/swift-algorithms.git", 70 | "state" : { 71 | "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", 72 | "version" : "1.2.1" 73 | } 74 | }, 75 | { 76 | "identity" : "swift-argument-parser", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/apple/swift-argument-parser.git", 79 | "state" : { 80 | "revision" : "011f0c765fb46d9cac61bca19be0527e99c98c8b", 81 | "version" : "1.5.1" 82 | } 83 | }, 84 | { 85 | "identity" : "swift-asn1", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/apple/swift-asn1.git", 88 | "state" : { 89 | "revision" : "a54383ada6cecde007d374f58f864e29370ba5c3", 90 | "version" : "1.3.2" 91 | } 92 | }, 93 | { 94 | "identity" : "swift-async-algorithms", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/apple/swift-async-algorithms.git", 97 | "state" : { 98 | "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", 99 | "version" : "1.0.4" 100 | } 101 | }, 102 | { 103 | "identity" : "swift-atomics", 104 | "kind" : "remoteSourceControl", 105 | "location" : "https://github.com/apple/swift-atomics.git", 106 | "state" : { 107 | "revision" : "cd142fd2f64be2100422d658e7411e39489da985", 108 | "version" : "1.2.0" 109 | } 110 | }, 111 | { 112 | "identity" : "swift-certificates", 113 | "kind" : "remoteSourceControl", 114 | "location" : "https://github.com/apple/swift-certificates.git", 115 | "state" : { 116 | "revision" : "999fd70c7803da89f3904d635a6815a2a7cd7585", 117 | "version" : "1.10.0" 118 | } 119 | }, 120 | { 121 | "identity" : "swift-cmark", 122 | "kind" : "remoteSourceControl", 123 | "location" : "https://github.com/swiftlang/swift-cmark.git", 124 | "state" : { 125 | "revision" : "b022b08312decdc46585e0b3440d97f6f22ef703", 126 | "version" : "0.6.0" 127 | } 128 | }, 129 | { 130 | "identity" : "swift-collections", 131 | "kind" : "remoteSourceControl", 132 | "location" : "https://github.com/apple/swift-collections.git", 133 | "state" : { 134 | "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", 135 | "version" : "1.2.0" 136 | } 137 | }, 138 | { 139 | "identity" : "swift-crypto", 140 | "kind" : "remoteSourceControl", 141 | "location" : "https://github.com/apple/swift-crypto.git", 142 | "state" : { 143 | "revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", 144 | "version" : "3.12.3" 145 | } 146 | }, 147 | { 148 | "identity" : "swift-distributed-tracing", 149 | "kind" : "remoteSourceControl", 150 | "location" : "https://github.com/apple/swift-distributed-tracing.git", 151 | "state" : { 152 | "revision" : "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", 153 | "version" : "1.2.0" 154 | } 155 | }, 156 | { 157 | "identity" : "swift-format", 158 | "kind" : "remoteSourceControl", 159 | "location" : "https://github.com/apple/swift-format.git", 160 | "state" : { 161 | "revision" : "7996ac678197d293f6c088a1e74bb778b4e10139", 162 | "version" : "510.1.0" 163 | } 164 | }, 165 | { 166 | "identity" : "swift-http-structured-headers", 167 | "kind" : "remoteSourceControl", 168 | "location" : "https://github.com/apple/swift-http-structured-headers.git", 169 | "state" : { 170 | "revision" : "db6eea3692638a65e2124990155cd220c2915903", 171 | "version" : "1.3.0" 172 | } 173 | }, 174 | { 175 | "identity" : "swift-http-types", 176 | "kind" : "remoteSourceControl", 177 | "location" : "https://github.com/apple/swift-http-types.git", 178 | "state" : { 179 | "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", 180 | "version" : "1.4.0" 181 | } 182 | }, 183 | { 184 | "identity" : "swift-log", 185 | "kind" : "remoteSourceControl", 186 | "location" : "https://github.com/apple/swift-log.git", 187 | "state" : { 188 | "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", 189 | "version" : "1.6.3" 190 | } 191 | }, 192 | { 193 | "identity" : "swift-markdown", 194 | "kind" : "remoteSourceControl", 195 | "location" : "https://github.com/apple/swift-markdown.git", 196 | "state" : { 197 | "revision" : "ea79e83c8744d2b50b0dc2d5bbd1e857e1253bf9", 198 | "version" : "0.6.0" 199 | } 200 | }, 201 | { 202 | "identity" : "swift-metrics", 203 | "kind" : "remoteSourceControl", 204 | "location" : "https://github.com/apple/swift-metrics.git", 205 | "state" : { 206 | "revision" : "4c83e1cdf4ba538ef6e43a9bbd0bcc33a0ca46e3", 207 | "version" : "2.7.0" 208 | } 209 | }, 210 | { 211 | "identity" : "swift-nio", 212 | "kind" : "remoteSourceControl", 213 | "location" : "https://github.com/apple/swift-nio.git", 214 | "state" : { 215 | "revision" : "34d486b01cd891297ac615e40d5999536a1e138d", 216 | "version" : "2.83.0" 217 | } 218 | }, 219 | { 220 | "identity" : "swift-nio-extras", 221 | "kind" : "remoteSourceControl", 222 | "location" : "https://github.com/apple/swift-nio-extras.git", 223 | "state" : { 224 | "revision" : "24cb15c9bc05e3e9eb5ebaf3d28517d42537bfb1", 225 | "version" : "1.27.1" 226 | } 227 | }, 228 | { 229 | "identity" : "swift-nio-http2", 230 | "kind" : "remoteSourceControl", 231 | "location" : "https://github.com/apple/swift-nio-http2.git", 232 | "state" : { 233 | "revision" : "4281466512f63d1bd530e33f4aa6993ee7864be0", 234 | "version" : "1.36.0" 235 | } 236 | }, 237 | { 238 | "identity" : "swift-nio-ssl", 239 | "kind" : "remoteSourceControl", 240 | "location" : "https://github.com/apple/swift-nio-ssl.git", 241 | "state" : { 242 | "revision" : "4b38f35946d00d8f6176fe58f96d83aba64b36c7", 243 | "version" : "2.31.0" 244 | } 245 | }, 246 | { 247 | "identity" : "swift-nio-transport-services", 248 | "kind" : "remoteSourceControl", 249 | "location" : "https://github.com/apple/swift-nio-transport-services.git", 250 | "state" : { 251 | "revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56", 252 | "version" : "1.24.0" 253 | } 254 | }, 255 | { 256 | "identity" : "swift-numerics", 257 | "kind" : "remoteSourceControl", 258 | "location" : "https://github.com/apple/swift-numerics.git", 259 | "state" : { 260 | "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", 261 | "version" : "1.0.3" 262 | } 263 | }, 264 | { 265 | "identity" : "swift-service-context", 266 | "kind" : "remoteSourceControl", 267 | "location" : "https://github.com/apple/swift-service-context.git", 268 | "state" : { 269 | "revision" : "8946c930cae601452149e45d31d8ddfac973c3c7", 270 | "version" : "1.2.0" 271 | } 272 | }, 273 | { 274 | "identity" : "swift-service-lifecycle", 275 | "kind" : "remoteSourceControl", 276 | "location" : "https://github.com/swift-server/swift-service-lifecycle.git", 277 | "state" : { 278 | "revision" : "e7187309187695115033536e8fc9b2eb87fd956d", 279 | "version" : "2.8.0" 280 | } 281 | }, 282 | { 283 | "identity" : "swift-syntax", 284 | "kind" : "remoteSourceControl", 285 | "location" : "https://github.com/apple/swift-syntax.git", 286 | "state" : { 287 | "revision" : "2bc86522d115234d1f588efe2bcb4ce4be8f8b82", 288 | "version" : "510.0.3" 289 | } 290 | }, 291 | { 292 | "identity" : "swift-system", 293 | "kind" : "remoteSourceControl", 294 | "location" : "https://github.com/apple/swift-system.git", 295 | "state" : { 296 | "revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6", 297 | "version" : "1.5.0" 298 | } 299 | }, 300 | { 301 | "identity" : "vapor", 302 | "kind" : "remoteSourceControl", 303 | "location" : "https://github.com/vapor/vapor.git", 304 | "state" : { 305 | "revision" : "4014016aad591a120f244f9b9e8a57252b7e62b4", 306 | "version" : "4.115.0" 307 | } 308 | }, 309 | { 310 | "identity" : "websocket-kit", 311 | "kind" : "remoteSourceControl", 312 | "location" : "https://github.com/vapor/websocket-kit.git", 313 | "state" : { 314 | "revision" : "8666c92dbbb3c8eefc8008c9c8dcf50bfd302167", 315 | "version" : "2.16.1" 316 | } 317 | } 318 | ], 319 | "version" : 2 320 | } 321 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "swiftfiddle-format", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.115.0"), 11 | .package(url: "https://github.com/vapor/leaf.git", from: "4.5.0"), 12 | .package(url: "https://github.com/apple/swift-format.git", from: "510.1.0"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "App", 17 | dependencies: [ 18 | .product(name: "Vapor", package: "vapor"), 19 | .product(name: "Leaf", package: "leaf"), 20 | .product(name: "SwiftFormat", package: "swift-format"), 21 | ], 22 | swiftSettings: [ 23 | .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) 24 | ] 25 | ), 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /Public/css/common.css: -------------------------------------------------------------------------------- 1 | .CodeMirror * { 2 | line-height: 1.6; 3 | font-family: Menlo, Consolas, "DejaVu Sans Mono", "Ubuntu Mono", monospace; 4 | font-size: 12px; 5 | max-height: 90vh; 6 | } 7 | 8 | @media (max-width: 767.98px) { 9 | .CodeMirror * { 10 | max-height: 50vh; 11 | } 12 | } 13 | 14 | .dropdown-list-item.active-tick a::after { 15 | font-family: "Font Awesome 6 Pro"; 16 | content: "\f00c"; 17 | color: #0d6efd; 18 | float: right; 19 | display: none; 20 | width: 1rem; 21 | height: 1.5rem; 22 | } 23 | 24 | .dropdown-list-item.active-tick svg { 25 | color: #0d6efd; 26 | float: right; 27 | width: 1rem; 28 | height: 1.5rem; 29 | } 30 | 31 | a .card-body { 32 | -o-transition: 0.5s; 33 | -ms-transition: 0.5s; 34 | -moz-transition: 0.5s; 35 | -webkit-transition: 0.5s; 36 | transition: 0.5s; 37 | } 38 | 39 | a .card-body:hover { 40 | background-color: #f8f9fa; 41 | } 42 | 43 | .svg-inline--fa.fa-fw { 44 | width: 1em; 45 | } 46 | -------------------------------------------------------------------------------- /Public/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | #(title) | #(status) - #(error) 29 | 332 | 333 | 334 | 335 |
336 |

#(error) Error #(status)

337 |

#(reason)

338 |
339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /Public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /Public/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /Public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /Public/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /Public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /Public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/favicon.ico -------------------------------------------------------------------------------- /Public/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /Public/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Public/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/favicons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /Public/images/ogp_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/images/ogp_image.png -------------------------------------------------------------------------------- /Public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 42 | 43 | Swift Formatter - Online Swift code formatter 44 | 45 | 46 | 47 |
48 |
49 |
50 |
51 | 52 | Swift Formatter 54 | 55 | 61 | 62 | 68 | 69 | 74 | 75 | 79 | 80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 |
92 | 93 |
94 | 95 |
96 |
97 |
98 |
99 | 129 |
130 | 131 | 132 |
134 |
135 |
136 | Configuration 137 |
138 | 139 | 145 | 151 | 155 | 156 | 157 | 159 |
160 | 161 |
162 |
163 |
164 |
Settings
165 |
166 | 167 |
168 | 169 |
170 | 172 |
173 |
174 | 175 |
176 | 177 |
178 | 180 |
181 |
182 | 183 |
184 | 185 |
186 | 188 |
189 |
190 | 191 |
192 | 193 |
194 |
195 | 197 | 201 | 203 |
204 |
205 |
206 | 207 |
208 | 211 |
212 |
213 | 214 |
215 |
216 |
217 | 218 |
219 | 222 |
223 |
224 | 225 |
226 |
227 |
228 | 229 |
230 | 233 |
234 |
235 | 236 |
237 |
238 |
239 | 240 |
241 | 244 |
245 |
246 | 247 |
248 |
249 |
250 | 251 |
252 | 255 |
256 |
257 | 258 |
259 |
260 |
261 | 262 |
263 | 266 |
267 |
268 | 269 |
270 |
271 |
272 | 273 |
274 | 277 |
278 |
279 | 280 |
281 |
282 |
283 | 284 |
285 | 288 |
289 | 291 | 295 |
296 |
297 | 298 |
299 | 302 |
303 |
304 | 305 |
306 |
307 |
308 | 309 |
310 |
Rules
311 |
312 | 313 |
314 | 317 |
318 |
319 | 320 |
321 |
322 |
323 | 324 |
325 | 328 |
329 |
330 | 331 |
332 |
333 |
334 | 335 |
336 | 339 |
340 |
341 | 342 |
343 |
344 |
345 | 346 |
347 | 350 |
351 |
352 | 353 |
354 |
355 |
356 | 357 |
358 | 361 |
362 |
363 | 364 |
365 |
366 |
367 | 368 |
369 | 372 |
373 |
374 | 375 |
376 |
377 |
378 | 379 |
380 | 383 |
384 |
385 | 386 |
387 |
388 |
389 | 390 |
391 | 394 |
395 |
396 | 397 |
398 |
399 |
400 | 401 |
402 | 405 |
406 |
407 | 408 |
409 |
410 |
411 | 412 |
413 | 416 |
417 |
418 | 419 |
420 |
421 |
422 | 423 |
424 | 427 |
428 |
429 | 430 |
431 |
432 |
433 | 434 |
435 | 438 |
439 |
440 | 441 |
442 |
443 |
444 | 445 |
446 | 449 |
450 |
451 | 452 |
453 |
454 |
455 | 456 |
457 | 460 |
461 |
462 | 463 |
464 |
465 |
466 | 467 |
468 | 471 |
472 |
473 | 474 |
475 |
476 |
477 | 478 |
479 | 482 |
483 |
484 | 485 |
486 |
487 |
488 | 489 |
490 | 493 |
494 |
495 | 496 |
497 |
498 |
499 | 500 |
501 | 504 |
505 |
506 | 507 |
508 |
509 |
510 | 511 |
512 | 515 |
516 |
517 | 518 |
519 |
520 |
521 | 522 |
523 | 526 |
527 |
528 | 529 |
530 |
531 |
532 | 533 |
534 | 537 |
538 |
539 | 540 |
541 |
542 |
543 | 544 |
545 | 548 |
549 |
550 | 551 |
552 |
553 |
554 | 555 |
556 | 559 |
560 |
561 | 562 |
563 |
564 |
565 | 566 |
567 | 570 |
571 |
572 | 573 |
574 |
575 |
576 | 577 |
578 | 581 |
582 |
583 | 584 |
585 |
586 |
587 | 588 |
589 | 592 |
593 |
594 | 595 |
596 |
597 |
598 | 599 |
600 | 603 |
604 |
605 | 606 |
607 |
608 |
609 | 610 |
611 | 614 |
615 |
616 | 617 |
618 |
619 |
620 | 621 |
622 | 625 |
626 |
627 | 628 |
629 |
630 |
631 | 632 |
633 | 636 |
637 |
638 | 639 |
640 |
641 |
642 | 643 |
644 | 647 |
648 |
649 | 650 |
651 |
652 |
653 | 654 |
655 | 658 |
659 |
660 | 661 |
662 |
663 |
664 |
665 |
666 |
667 | 668 | 669 |
670 |
671 |
672 |
673 |

Experience an online playground for swift-format, allowing for rapid testing of diverse 677 | configurations directly on the web.

678 |

For a comprehensive understanding of swift-format, delve into the repository at apple/swift-format 681 | .

682 |
683 |
684 |
685 |
686 | 687 | 688 |
689 | 699 |
700 |
701 | 712 |
713 | 714 | 715 | 716 | -------------------------------------------------------------------------------- /Public/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import "./scss/default.scss"; 4 | 5 | import "codemirror/lib/codemirror.css"; 6 | import "codemirror/addon/lint/lint.css"; 7 | 8 | import "./css/common.css"; 9 | 10 | import CodeMirror from "codemirror"; 11 | import "codemirror/mode/swift/swift"; 12 | import "codemirror/addon/display/panel"; 13 | import "codemirror/addon/edit/matchbrackets"; 14 | import "codemirror/addon/edit/closebrackets"; 15 | import "codemirror/addon/edit/trailingspace"; 16 | import "codemirror/addon/lint/lint"; 17 | 18 | import "./js/icon.js"; 19 | import "./js/about.js"; 20 | 21 | import { Tooltip } from "bootstrap"; 22 | import { SwiftFormat } from "./js/swift_format.js"; 23 | import { Snackbar } from "./js/snackbar.js"; 24 | import { Defaults } from "./js/defaults.js"; 25 | import { buildConfiguration, resetConfiguration } from "./js/configuration.js"; 26 | import { initEditor } from "./js/editor.js"; 27 | import { initResultView } from "./js/result_view.js"; 28 | import { debounce } from "./js/debounce.js"; 29 | 30 | const editor = initEditor( 31 | document.getElementById("editor-container"), 32 | document.getElementById("editor-statusbar") 33 | ); 34 | 35 | editor.setValue(Defaults.code); 36 | editor.clearHistory(); 37 | editor.focus(); 38 | editor.setCursor({ line: editor.lastLine() + 1, ch: 0 }); 39 | 40 | const resultView = initResultView( 41 | document.getElementById("result-container"), 42 | document.getElementById("result-statusbar") 43 | ); 44 | 45 | document.getElementById("clear-button").classList.remove("disabled"); 46 | 47 | const formatterService = new SwiftFormat(); 48 | formatterService.onready = () => { 49 | document.getElementById("run-button").classList.remove("disabled"); 50 | sendFormatRequest(); 51 | }; 52 | 53 | formatterService.onresponse = (response) => { 54 | if (response) { 55 | if (response.output.trim()) { 56 | resultView.setValue(response.output); 57 | } else { 58 | resultView.setValue(response.original); 59 | } 60 | 61 | editor.setOption("lint", { 62 | getAnnotations: () => { 63 | const message = response.lintMessage; 64 | const matches = message.matchAll( 65 | /:(\d+):(\d+): (error|warning|note): ([\s\S]*?)\n*(?=(?:\/|$))/gi 66 | ); 67 | return [...matches].map((match) => { 68 | const row = +match[1] - 1; // 0 origin 69 | const column = +match[2]; 70 | const text = match[4]; 71 | const severity = match[3]; 72 | 73 | return { 74 | from: CodeMirror.Pos(row, column), 75 | to: CodeMirror.Pos(row, column + 1), 76 | message: text, 77 | severity: severity, 78 | }; 79 | }); 80 | }, 81 | }); 82 | 83 | if (response.error) { 84 | Snackbar.alert(response.error); 85 | } 86 | } 87 | }; 88 | 89 | const updateOnTextChange = debounce(() => { 90 | sendFormatRequest(); 91 | }, 400); 92 | editor.on("change", () => { 93 | updateOnTextChange(); 94 | }); 95 | 96 | document.querySelectorAll(".dropdown-list-item").forEach((listItem) => { 97 | listItem.addEventListener("click", () => { 98 | for (let sibling of listItem.parentNode.children) { 99 | sibling.classList.remove("active-tick"); 100 | } 101 | listItem.classList.add("active-tick"); 102 | 103 | listItem.parentNode.previousElementSibling.textContent = 104 | listItem.querySelector(".dropdown-item").textContent; 105 | 106 | sendFormatRequest(); 107 | }); 108 | }); 109 | 110 | const form = document.querySelector("form"); 111 | form.addEventListener("input", () => { 112 | sendFormatRequest(); 113 | }); 114 | 115 | document.getElementById("run-button").addEventListener("click", () => { 116 | sendFormatRequest(); 117 | }); 118 | 119 | document.getElementById("clear-button").addEventListener("click", () => { 120 | editor.setValue(""); 121 | editor.clearHistory(); 122 | resultView.setValue(""); 123 | resultView.clearHistory(); 124 | }); 125 | 126 | document.getElementById("reset-config-button").addEventListener("click", () => { 127 | resetConfiguration(); 128 | sendFormatRequest(); 129 | }); 130 | 131 | if (!navigator.clipboard) { 132 | document.getElementById("copy-config-button").classList.add("disabled"); 133 | } 134 | document.getElementById("copy-config-button").addEventListener("click", () => { 135 | if (navigator.clipboard) { 136 | const configuration = buildConfiguration(); 137 | navigator.clipboard.writeText(JSON.stringify(configuration, null, 2)); 138 | Snackbar.info("Copied!"); 139 | } 140 | }); 141 | 142 | function sendFormatRequest() { 143 | const value = editor.getValue(); 144 | if (!value.trim()) { 145 | return; 146 | } 147 | 148 | document.getElementById("run-button-icon").classList.add("d-none"); 149 | document.getElementById("run-button-spinner").classList.remove("d-none"); 150 | setTimeout(() => { 151 | document.getElementById("run-button-icon").classList.remove("d-none"); 152 | document.getElementById("run-button-spinner").classList.add("d-none"); 153 | }, 600); 154 | 155 | formatterService.format({ 156 | code: value, 157 | configuration: buildConfiguration(), 158 | }); 159 | } 160 | 161 | [].slice 162 | .call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) 163 | .map((trigger) => { 164 | return new Tooltip(trigger); 165 | }); 166 | -------------------------------------------------------------------------------- /Public/js/about.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Popover, Tooltip } from "bootstrap"; 4 | 5 | const aboutButton = document.getElementById("about-button"); 6 | const popoverContent = document.getElementById("about-popover"); 7 | const popover = new Popover(aboutButton, { 8 | title: "", 9 | trigger: "manual", 10 | html: true, 11 | content: popoverContent, 12 | container: "body", 13 | }); 14 | 15 | aboutButton.addEventListener("show.bs.popover", () => { 16 | popoverContent.classList.remove("d-none"); 17 | }); 18 | 19 | aboutButton.addEventListener("shown.bs.popover", () => { 20 | document.querySelectorAll(".tooltip").forEach((tooltip) => { 21 | Tooltip.getInstance(tooltip).hide(); 22 | }); 23 | }); 24 | 25 | aboutButton.addEventListener("click", (event) => { 26 | popover.toggle(); 27 | event.stopPropagation(); 28 | }); 29 | 30 | document.body.addEventListener("click", (event) => { 31 | if (event.target !== aboutButton && !event.target.closest(".popover")) { 32 | popover.hide(); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /Public/js/configuration.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const form = document.querySelector("form"); 4 | 5 | export function buildConfiguration() { 6 | const configuration = JSON.parse(JSON.stringify(Configuration.default)); 7 | 8 | [...form.getElementsByTagName("input")].forEach((input) => { 9 | if (input.id === "indentationCount") { 10 | if (input.value) { 11 | const indent = document.getElementById("indentation").textContent; 12 | if (indent) { 13 | const indentation = {}; 14 | indentation[indent.toLowerCase()] = parseInt(input.value); 15 | configuration["indentation"] = indentation; 16 | } 17 | } 18 | } else if (input.type === "checkbox") { 19 | configuration[input.id] = input.checked; 20 | } else { 21 | if (input.value) { 22 | configuration[input.id] = parseInt(input.value); 23 | } 24 | } 25 | }); 26 | 27 | const accessLevel = document.getElementById( 28 | "fileScopedDeclarationPrivacy" 29 | ).textContent; 30 | 31 | configuration["fileScopedDeclarationPrivacy"] = { accessLevel: accessLevel }; 32 | 33 | return configuration; 34 | } 35 | 36 | export function resetConfiguration() { 37 | const configuration = JSON.parse(JSON.stringify(Configuration.default)); 38 | 39 | [...form.getElementsByTagName("input")].forEach((input) => { 40 | if (input.id === "indentationCount") { 41 | input.value = null; 42 | document.getElementById("indentation").textContent = "Spaces"; 43 | } else if (input.type === "checkbox") { 44 | if (configuration[input.id]) { 45 | input.checked = configuration[input.id]; 46 | } else { 47 | input.checked = configuration.rules[input.id]; 48 | } 49 | } else { 50 | input.value = null; 51 | } 52 | }); 53 | 54 | document.getElementById("fileScopedDeclarationPrivacy").textContent = 55 | "private"; 56 | 57 | document.querySelectorAll(".dropdown-list-item").forEach((listItem) => { 58 | for (let sibling of listItem.parentNode.children) { 59 | sibling.classList.remove("active-tick"); 60 | } 61 | for (let sibling of listItem.parentNode.children) { 62 | for (let child of sibling.children) { 63 | if (child.textContent == "Spaces" || child.textContent == "private") { 64 | sibling.classList.add("active-tick"); 65 | } 66 | } 67 | } 68 | }); 69 | } 70 | 71 | class Configuration { 72 | constructor() {} 73 | 74 | static default = { 75 | version: 1, 76 | maximumBlankLines: 1, 77 | lineLength: 100, 78 | tabWidth: 8, 79 | indentation: { 80 | spaces: 2, 81 | }, 82 | respectsExistingLineBreaks: true, 83 | lineBreakBeforeControlFlowKeywords: false, 84 | lineBreakBeforeEachArgument: false, 85 | lineBreakBeforeEachGenericRequirement: false, 86 | prioritizeKeepingFunctionOutputTogether: false, 87 | indentConditionalCompilationBlocks: true, 88 | lineBreakAroundMultilineExpressionChainComponents: false, 89 | fileScopedDeclarationPrivacy: { 90 | accessLevel: "private", 91 | }, 92 | indentSwitchCaseLabels: false, 93 | rules: { 94 | AllPublicDeclarationsHaveDocumentation: false, 95 | AlwaysUseLowerCamelCase: true, 96 | AmbiguousTrailingClosureOverload: true, 97 | BeginDocumentationCommentWithOneLineSummary: false, 98 | DoNotUseSemicolons: true, 99 | DontRepeatTypeInStaticProperties: true, 100 | FileScopedDeclarationPrivacy: true, 101 | FullyIndirectEnum: true, 102 | GroupNumericLiterals: true, 103 | IdentifiersMustBeASCII: true, 104 | NeverForceUnwrap: false, 105 | NeverUseForceTry: false, 106 | NeverUseImplicitlyUnwrappedOptionals: false, 107 | NoAccessLevelOnExtensionDeclaration: true, 108 | NoBlockComments: true, 109 | NoCasesWithOnlyFallthrough: true, 110 | NoEmptyTrailingClosureParentheses: true, 111 | NoLabelsInCasePatterns: true, 112 | NoLeadingUnderscores: false, 113 | NoParensAroundConditions: true, 114 | NoVoidReturnOnFunctionSignature: true, 115 | OneCasePerLine: true, 116 | OneVariableDeclarationPerLine: true, 117 | OnlyOneTrailingClosureArgument: true, 118 | OrderedImports: true, 119 | ReturnVoidInsteadOfEmptyTuple: true, 120 | UseLetInEveryBoundCaseVariable: true, 121 | UseShorthandTypeNames: true, 122 | UseSingleLinePropertyGetter: true, 123 | UseSynthesizedInitializer: true, 124 | UseTripleSlashForDocumentationComments: true, 125 | ValidateDocumentationComments: false, 126 | }, 127 | }; 128 | } 129 | -------------------------------------------------------------------------------- /Public/js/debounce.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export function debounce(fn, interval) { 4 | let timerId; 5 | return () => { 6 | clearTimeout(timerId); 7 | const context = this; 8 | const args = arguments; 9 | timerId = setTimeout(() => { 10 | fn.apply(context, args); 11 | }, interval); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /Public/js/defaults.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export const Defaults = { 4 | code: `import UIKit 5 | 6 | struct Timeline: RandomAccessCollection { 7 | var storage: [Date: Element] = [:] 8 | 9 | var startIndex = DateIndex(Date.distantPast) 10 | var endIndex = DateIndex(Date.distantPast) 11 | 12 | subscript(i: DateIndex) -> Element? { 13 | get { 14 | return storage[i.date] 15 | } 16 | set { 17 | if isEmpty { 18 | startIndex = i 19 | endIndex = index(after: i) 20 | } else if i < startIndex { 21 | startIndex = i 22 | } else if i >= endIndex { 23 | endIndex = index(after: i) 24 | } 25 | 26 | storage[i.date] = newValue 27 | } 28 | } 29 | 30 | func index(after i: DateIndex) -> DateIndex { 31 | let nextDay = calendar.date(byAdding: DateComponents(day: 1), to: i.date)! 32 | return DateIndex(nextDay) 33 | } 34 | 35 | func distance(from start: DateIndex, to end: DateIndex) -> Int { 36 | return calendar.dateComponents([.day], from: start.date, to: end.date).day! 37 | } 38 | } 39 | `, 40 | }; 41 | -------------------------------------------------------------------------------- /Public/js/editor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import CodeMirror from "codemirror"; 4 | 5 | export function initEditor(container, statusbar) { 6 | const editor = CodeMirror.fromTextArea(container, { 7 | mode: "swift", 8 | lineNumbers: true, 9 | lineWrapping: false, 10 | tabSize: 2, 11 | screenReaderLabel: "Editor Pane", 12 | matchBrackets: true, 13 | autoCloseBrackets: true, 14 | showTrailingSpace: true, 15 | gutters: ["CodeMirror-lint-markers"], 16 | lint: true, 17 | }); 18 | editor.setSize("100%", `calc(100% - 18px)`); 19 | 20 | editor.on("cursorActivity", () => { 21 | const cursor = editor.getCursor(); 22 | const line = cursor.line + 1; 23 | const col = cursor.ch + 1; 24 | statusbar.textContent = `Ln ${line}, Col ${col}`; 25 | }); 26 | 27 | return editor; 28 | } 29 | -------------------------------------------------------------------------------- /Public/js/icon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { config, library, dom } from "@fortawesome/fontawesome-svg-core"; 4 | import { 5 | faPlay, 6 | faCircleNotch, 7 | faEraser, 8 | faCog, 9 | faUndo, 10 | faQuestion, 11 | faExclamationTriangle, 12 | faHeart, 13 | } from "@fortawesome/pro-solid-svg-icons"; 14 | import { 15 | faToolbox, 16 | faCheck, 17 | faClipboard, 18 | faCheckCircle, 19 | faCommentAltSmile, 20 | faAt, 21 | } from "@fortawesome/pro-regular-svg-icons"; 22 | import { 23 | faSlidersV, 24 | faRulerTriangle, 25 | faMonitorHeartRate, 26 | } from "@fortawesome/pro-light-svg-icons"; 27 | import { faSpinnerThird } from "@fortawesome/pro-duotone-svg-icons"; 28 | import { faSwift, faGithub } from "@fortawesome/free-brands-svg-icons"; 29 | 30 | config.searchPseudoElements = true; 31 | library.add( 32 | faPlay, 33 | faCircleNotch, 34 | faEraser, 35 | faCog, 36 | faUndo, 37 | faQuestion, 38 | faExclamationTriangle, 39 | faHeart, 40 | 41 | faToolbox, 42 | faCheck, 43 | faClipboard, 44 | faCheckCircle, 45 | faCommentAltSmile, 46 | faAt, 47 | 48 | faSlidersV, 49 | faRulerTriangle, 50 | faMonitorHeartRate, 51 | 52 | faSpinnerThird, 53 | 54 | faSwift, 55 | faGithub 56 | ); 57 | dom.watch(); 58 | -------------------------------------------------------------------------------- /Public/js/result_view.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import CodeMirror from "codemirror"; 4 | 5 | export function initResultView(container, statusbar) { 6 | const editor = CodeMirror.fromTextArea(container, { 7 | mode: "swift", 8 | lineNumbers: true, 9 | lineWrapping: false, 10 | readOnly: true, 11 | screenReaderLabel: "Result Pane", 12 | matchBrackets: true, 13 | showTrailingSpace: true, 14 | }); 15 | editor.setSize("100%", `calc(100% - 18px)`); 16 | 17 | editor.on("cursorActivity", () => { 18 | const cursor = editor.getCursor(); 19 | const line = cursor.line + 1; 20 | const col = cursor.ch + 1; 21 | statusbar.textContent = `Ln ${line}, Col ${col}`; 22 | }); 23 | 24 | return editor; 25 | } 26 | -------------------------------------------------------------------------------- /Public/js/snackbar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Toast } from "bootstrap"; 4 | 5 | const infoBlock = document.getElementById("snackbar-info"); 6 | const alertBlock = document.getElementById("snackbar-alert"); 7 | 8 | export class Snackbar { 9 | static info(message) { 10 | const messageContainer = document.getElementById("snackbar-info-message"); 11 | messageContainer.innerText = message; 12 | infoBlock.classList.remove("d-none"); 13 | new Toast(infoBlock).show(); 14 | } 15 | 16 | static alert(message) { 17 | const messageContainer = document.getElementById("snackbar-alert-message"); 18 | messageContainer.innerText = message; 19 | alertBlock.classList.remove("d-none"); 20 | new Toast(alertBlock).show(); 21 | } 22 | } 23 | 24 | if (infoBlock) { 25 | infoBlock.addEventListener("hidden.bs.toast", () => { 26 | infoBlock.classList.add("d-none"); 27 | }); 28 | infoBlock.addEventListener("hidden.bs.toast", () => { 29 | infoBlock.classList.add("d-none"); 30 | }); 31 | } 32 | if (alertBlock) { 33 | alertBlock.addEventListener("hidden.bs.toast", () => { 34 | alertBlock.classList.add("d-none"); 35 | }); 36 | alertBlock.addEventListener("hidden.bs.toast", () => { 37 | alertBlock.classList.add("d-none"); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /Public/js/swift_format.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import ReconnectingWebSocket from "reconnecting-websocket"; 4 | 5 | export class SwiftFormat { 6 | constructor() { 7 | this.connection = this.createConnection(this.endpoint()); 8 | 9 | this.onconnect = () => {}; 10 | this.onready = () => {}; 11 | this.onresponse = () => {}; 12 | } 13 | 14 | get isReady() { 15 | return this.connection.readyState === 1; 16 | } 17 | 18 | format(request) { 19 | const encoder = new TextEncoder(); 20 | this.connection.send(encoder.encode(JSON.stringify(request))); 21 | } 22 | 23 | createConnection(endpoint) { 24 | if ( 25 | this.connection && 26 | (this.connection.readyState === 0 || this.connection.readyState === 1) 27 | ) { 28 | return this.connection; 29 | } 30 | 31 | const connection = new ReconnectingWebSocket(endpoint, [], { 32 | maxReconnectionDelay: 10000, 33 | minReconnectionDelay: 1000, 34 | reconnectionDelayGrowFactor: 1.3, 35 | connectionTimeout: 10000, 36 | maxRetries: Infinity, 37 | debug: false, 38 | }); 39 | connection.bufferType = "arraybuffer"; 40 | 41 | connection.onopen = () => { 42 | this.onconnect(); 43 | this.onready(); 44 | }; 45 | 46 | connection.onerror = (event) => { 47 | connection.close(); 48 | }; 49 | 50 | connection.onmessage = (event) => { 51 | if (event.data.trim()) { 52 | this.onresponse(JSON.parse(event.data)); 53 | } 54 | }; 55 | return connection; 56 | } 57 | 58 | endpoint() { 59 | let endpoint; 60 | if (window.location.protocol === "https:") { 61 | endpoint = "wss:"; 62 | } else { 63 | endpoint = "ws:"; 64 | } 65 | endpoint += "//" + window.location.host; 66 | endpoint += window.location.pathname + "api/ws"; 67 | return endpoint; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Public/robots.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftFiddle/swiftfiddle-formatter/e11c8d3104b2b973e7d8f826ec89f47c522043ee/Public/robots.txt -------------------------------------------------------------------------------- /Public/scss/default.scss: -------------------------------------------------------------------------------- 1 | $popover-max-width: 400px; 2 | $toast-max-width: 400px; 3 | $input-border-color: #6c757d; 4 | $input-placeholder-color: #adb5bd; 5 | 6 | @import "bootstrap/scss/functions"; 7 | @import "bootstrap/scss/variables"; 8 | @import "bootstrap/scss/variables-dark"; 9 | @import "bootstrap/scss/mixins"; 10 | @import "bootstrap/scss/maps"; 11 | @import "bootstrap/scss/utilities"; 12 | 13 | @import "bootstrap/scss/root"; 14 | @import "bootstrap/scss/reboot"; 15 | @import "bootstrap/scss/type"; 16 | @import "bootstrap/scss/containers"; 17 | @import "bootstrap/scss/grid"; 18 | @import "bootstrap/scss/forms"; 19 | @import "bootstrap/scss/buttons"; 20 | @import "bootstrap/scss/transitions"; 21 | @import "bootstrap/scss/dropdown"; 22 | @import "bootstrap/scss/card"; 23 | @import "bootstrap/scss/close"; 24 | @import "bootstrap/scss/toasts"; 25 | @import "bootstrap/scss/modal"; 26 | @import "bootstrap/scss/tooltip"; 27 | @import "bootstrap/scss/popover"; 28 | @import "bootstrap/scss/offcanvas"; 29 | 30 | @import "bootstrap/scss/utilities/api"; 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftfiddle-formatter 2 | 3 | Experience the ease of Swift code formatting online - enhance readability, improve aesthetics, and ensure correct indentation, all at your fingertips. 4 | 5 | Visit us at: https://swift-format.com 6 | 7 | SwiftFiddle Formatter is a versatile playground for experimenting with swift-format. Swiftly try out various configurations of swift-format right in your browser. 8 | 9 | To dig deeper into swift-format, we recommend checking out its repository: 10 | apple/swift-format 11 | 12 | Preview our user-friendly interface: 13 | 14 | Screen Shot 2021-08-11 at 8 41 48 15 | 16 | Experience our intuitive code editing environment: 17 | 18 | Screen Shot 2021-08-11 at 8 41 54 19 | 20 | ## Author 21 | 22 | [Kishikawa Katsumi](https://github.com/kishikawakatsumi) 23 | 24 | ## Supporters & Sponsors 25 | 26 | Open source projects thrive on the generosity and support of people like you. If you find this project valuable, please consider extending your support. Contributing to the project not only sustains its growth, but also helps drive innovation and improve its features. 27 | 28 | To support this project, you can become a sponsor through [GitHub Sponsors](https://github.com/sponsors/kishikawakatsumi). Your contribution will be greatly appreciated and will help keep the project alive and thriving. Thanks for your consideration! :heart: 29 | -------------------------------------------------------------------------------- /Resources/formatter/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Resources/formatter/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "82905286cc3f0fa8adc4674bf49437cab65a8373", 10 | "version": "1.1.1" 11 | } 12 | }, 13 | { 14 | "package": "swift-format", 15 | "repositoryURL": "https://github.com/apple/swift-format.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "fbfe1869527923dd9f9b2edac148baccfce0dce7", 19 | "version": "508.0.1" 20 | } 21 | }, 22 | { 23 | "package": "SwiftSyntax", 24 | "repositoryURL": "https://github.com/apple/swift-syntax", 25 | "state": { 26 | "branch": null, 27 | "revision": "2c49d66d34dfd6f8130afdba889de77504b58ec0", 28 | "version": "508.0.1" 29 | } 30 | }, 31 | { 32 | "package": "swift-system", 33 | "repositoryURL": "https://github.com/apple/swift-system.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "836bc4557b74fe6d2660218d56e3ce96aff76574", 37 | "version": "1.1.1" 38 | } 39 | }, 40 | { 41 | "package": "swift-tools-support-core", 42 | "repositoryURL": "https://github.com/apple/swift-tools-support-core.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "93784c59434dbca8e8a9e4b700d0d6d94551da6a", 46 | "version": "0.5.2" 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Resources/formatter/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "formatter", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/apple/swift-format.git", from: "510.1.0"), 11 | ] 12 | ) 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | For security related problems, please don't use the public issue tracker, but email [@kishikawakatsumi](https://github.com/kishikawakatsumi). 4 | -------------------------------------------------------------------------------- /Sources/App/Middlewares/CommonErrorMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | final class CommonErrorMiddleware: Middleware { 4 | func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { 5 | return next.respond(to: request).flatMapError { (error) in 6 | let headers: HTTPHeaders 7 | let status: HTTPResponseStatus 8 | switch error { 9 | case let abort as AbortError: 10 | headers = abort.headers 11 | status = abort.status 12 | default: 13 | headers = [:] 14 | status = .internalServerError 15 | } 16 | 17 | let errotTitles: [UInt: String] = [ 18 | 400: "Bad Request", 19 | 401: "Unauthorized", 20 | 403: "Access Denied", 21 | 404: "Resource not found", 22 | 500: "Webservice currently unavailable", 23 | 503: "Webservice currently unavailable", 24 | ] 25 | 26 | let errotReasons: [UInt: String] = [ 27 | 400: "The server cannot process the request due to something that is perceived to be a client error.", 28 | 401: "The requested resource requires an authentication.", 29 | 403: "The requested resource requires an authentication.", 30 | 404: "The requested resource could not be found but may be available again in the future.", 31 | 500: "An unexpected condition was encountered. Our service team has been dispatched to bring it back online.", 32 | 503: "We've got some trouble with our backend upstream cluster. Our service team has been dispatched to bring it back online.", 33 | ] 34 | 35 | if request.headers[.accept].map({ $0.lowercased() }).contains("application/json") { 36 | return request.eventLoop.makeSucceededFuture(["error": status.code]) 37 | .encodeResponse(status: status, headers: headers, for: request) 38 | } else { 39 | return request.view.render("error", [ 40 | "title": "We've got some trouble", 41 | "error": errotTitles[status.code], 42 | "reason": errotReasons[status.code], 43 | "status": "\(status.code)", 44 | ]) 45 | .encodeResponse(status: status, headers: headers, for: request) 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/App/Middlewares/CustomHeaderMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | final class CustomHeaderMiddleware: Middleware { 4 | func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { 5 | return next.respond(to: request).map { (response) in 6 | response.headers.add(name: "X-Frame-Options", value: "DENY") 7 | response.headers.add(name: "Permissions-Policy", value: "interest-cohort=()") 8 | return response 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Leaf 3 | 4 | public func configure(_ app: Application) async throws { 5 | app.middleware = Middlewares() 6 | app.middleware.use(CommonErrorMiddleware()) 7 | app.middleware.use(CustomHeaderMiddleware()) 8 | 9 | let publicDirectory = "\(app.directory.publicDirectory)/dist" 10 | app.middleware.use(FileMiddleware(publicDirectory: publicDirectory)) 11 | 12 | app.http.server.configuration.port = Environment.process.PORT.flatMap { Int($0) } ?? 8080 13 | app.http.server.configuration.requestDecompression = .enabled 14 | app.http.server.configuration.responseCompression = .enabled 15 | app.http.server.configuration.supportPipelining = true 16 | 17 | app.views.use(.leaf) 18 | app.leaf.configuration.rootDirectory = publicDirectory 19 | app.leaf.cache.isEnabled = app.environment.isRelease 20 | 21 | try routes(app) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/App/entrypoint.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | @main 4 | enum Entrypoint { 5 | static func main() async throws { 6 | var env = try Environment.detect() 7 | try LoggingSystem.bootstrap(from: &env) 8 | 9 | let app = try await Application.make(env) 10 | 11 | do { 12 | try await configure(app) 13 | try await app.execute() 14 | } catch { 15 | app.logger.report(error: error) 16 | try? await app.asyncShutdown() 17 | throw error 18 | } 19 | try await app.asyncShutdown() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Vapor 3 | import SwiftFormatConfiguration 4 | 5 | func routes(_ app: Application) throws { 6 | app.get("health") { _ in ["status": "pass"] } 7 | app.get("healthz") { _ in ["status": "pass"] } 8 | 9 | app.get { (req) in 10 | return req.view.render("index") 11 | } 12 | 13 | app.webSocket("api", "ws") { (req, ws) in 14 | ws.onBinary { (ws, buffer) in 15 | do { 16 | guard let requestData = buffer.getData(at: 0, length: buffer.readableBytes) else { return } 17 | 18 | let decoder = JSONDecoder() 19 | let request = try decoder.decode(FormatRequest.self, from: requestData) 20 | 21 | let (output, error) = try format( 22 | source: request.code, 23 | configuration: request.configuration 24 | ) 25 | let (_, lintMessage) = try lint( 26 | source: request.code, 27 | configuration: request.configuration 28 | ) 29 | 30 | let encoder = JSONEncoder() 31 | let response = FormatResponse( 32 | output: output, 33 | error: error, 34 | lintMessage: lintMessage, 35 | original: request.code 36 | ) 37 | if let message = String(data: try encoder.encode(response), encoding: .utf8) { 38 | ws.send(message) 39 | } 40 | } catch { 41 | req.logger.error("\(error)") 42 | ws.send("") 43 | } 44 | } 45 | } 46 | 47 | app.on(.POST, "api", body: .collect(maxSize: "10mb")) { (req) -> FormatResponse in 48 | guard let request = try? req.content.decode(FormatRequest.self) else { 49 | throw Abort(.badRequest) 50 | } 51 | 52 | let (output, error) = try format( 53 | source: request.code, 54 | configuration: request.configuration 55 | ) 56 | let (_, lintMessage) = try lint( 57 | source: request.code, 58 | configuration: request.configuration 59 | ) 60 | 61 | let response = FormatResponse( 62 | output: output, 63 | error: error, 64 | lintMessage: lintMessage, 65 | original: request.code 66 | ) 67 | return response 68 | } 69 | 70 | func format(source: String, configuration: Configuration?) throws -> (stdout: String, stderr: String) { 71 | return try exec(subcommand: "format", source: source, configuration: configuration) 72 | } 73 | 74 | func lint(source: String, configuration: Configuration?) throws -> (stdout: String, stderr: String) { 75 | return try exec(subcommand: "lint", source: source, configuration: configuration) 76 | } 77 | 78 | func exec(subcommand: String, source: String, configuration: Configuration?) throws -> (stdout: String, stderr: String) { 79 | guard let input = source.data(using: .utf8) else { 80 | throw Abort(.badRequest) 81 | } 82 | 83 | let temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()) 84 | let configurationFile = temporaryDirectory.appendingPathComponent("\(UUID().uuidString).json") 85 | let encoder = JSONEncoder() 86 | try encoder 87 | .encode(configuration ?? Configuration()) 88 | .write(to: configurationFile) 89 | 90 | let standardInput = Pipe() 91 | let standardOutput = Pipe() 92 | let standardError = Pipe() 93 | 94 | let fileHandle = standardInput.fileHandleForWriting 95 | fileHandle.write(input) 96 | try fileHandle.close() 97 | 98 | let process = Process() 99 | let executableURL = URL( 100 | fileURLWithPath: "\(app.directory.resourcesDirectory)formatter/.build/release/swift-format" 101 | ) 102 | process.executableURL = executableURL 103 | process.arguments = [subcommand, "--configuration", configurationFile.path] 104 | 105 | process.standardInput = standardInput 106 | process.standardOutput = standardOutput 107 | process.standardError = standardError 108 | 109 | try process.run() 110 | process.waitUntilExit() 111 | 112 | try? FileManager().removeItem(at: configurationFile) 113 | 114 | let stdoutData = standardOutput.fileHandleForReading.readDataToEndOfFile() 115 | guard let stdout = String(data: stdoutData, encoding: .utf8) else { 116 | throw Abort(.internalServerError) 117 | } 118 | 119 | let stderrData = standardError.fileHandleForReading.readDataToEndOfFile() 120 | guard let stderr = String(data: stderrData, encoding: .utf8) else { 121 | throw Abort(.internalServerError) 122 | } 123 | 124 | return (stdout, stderr) 125 | } 126 | } 127 | 128 | private struct FormatRequest: Codable { 129 | let code: String 130 | let configuration: Configuration? 131 | } 132 | 133 | private struct FormatResponse: Content { 134 | let output: String 135 | let error: String 136 | let lintMessage: String 137 | let original: String 138 | } 139 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for swift-format on 2023-05-28T18:52:15+09:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = "swift-format" 7 | kill_signal = "SIGINT" 8 | kill_timeout = "5s" 9 | 10 | [experimental] 11 | auto_rollback = true 12 | 13 | [[services]] 14 | protocol = "tcp" 15 | internal_port = 8080 16 | processes = ["app"] 17 | 18 | [[services.ports]] 19 | port = 80 20 | handlers = ["http"] 21 | force_https = true 22 | 23 | [[services.ports]] 24 | port = 443 25 | handlers = ["tls", "http"] 26 | [services.concurrency] 27 | type = "connections" 28 | hard_limit = 25 29 | soft_limit = 20 30 | 31 | [[services.tcp_checks]] 32 | interval = "15s" 33 | timeout = "2s" 34 | grace_period = "1s" 35 | restart_limit = 0 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "prod": "webpack --progress --config webpack.prod.js", 4 | "dev": "webpack --progress --config webpack.dev.js" 5 | }, 6 | "dependencies": { 7 | "@fortawesome/fontawesome-svg-core": "6.7.2", 8 | "@fortawesome/free-brands-svg-icons": "6.7.2", 9 | "@fortawesome/pro-duotone-svg-icons": "6.4.0", 10 | "@fortawesome/pro-light-svg-icons": "6.4.0", 11 | "@fortawesome/pro-regular-svg-icons": "6.4.0", 12 | "@fortawesome/pro-solid-svg-icons": "6.4.0", 13 | "@popperjs/core": "2.11.8", 14 | "bootstrap": "5.3.6", 15 | "codemirror": "5.65.19", 16 | "reconnecting-websocket": "4.4.0" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "10.4.21", 20 | "copy-webpack-plugin": "13.0.0", 21 | "css-loader": "7.1.2", 22 | "html-webpack-plugin": "5.6.3", 23 | "mini-css-extract-plugin": "2.9.2", 24 | "postcss": "8.5.4", 25 | "postcss-loader": "8.1.1", 26 | "sass": "1.89.1", 27 | "sass-loader": "16.0.5", 28 | "style-loader": "4.0.0", 29 | "webpack": "5.99.9", 30 | "webpack-bundle-analyzer": "4.10.2", 31 | "webpack-cli": "6.0.1", 32 | "webpack-merge": "6.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const CopyWebbackPlugin = require("copy-webpack-plugin"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | 6 | module.exports = { 7 | entry: { 8 | index: "./Public/index.js", 9 | }, 10 | output: { 11 | globalObject: "self", 12 | filename: "[name].[contenthash].js", 13 | path: path.resolve(__dirname, "Public/dist"), 14 | publicPath: "/", 15 | clean: true, 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.scss$/, 21 | use: [ 22 | { 23 | loader: MiniCssExtractPlugin.loader, 24 | }, 25 | { 26 | loader: "css-loader", 27 | options: { 28 | url: false, 29 | sourceMap: true, 30 | importLoaders: 2, 31 | }, 32 | }, 33 | { 34 | loader: "postcss-loader", 35 | options: { 36 | sourceMap: true, 37 | postcssOptions: { 38 | plugins: ["autoprefixer"], 39 | }, 40 | }, 41 | }, 42 | { 43 | loader: "sass-loader", 44 | options: { 45 | sourceMap: true, 46 | }, 47 | }, 48 | ], 49 | }, 50 | { 51 | test: /\.css$/, 52 | use: ["style-loader", "css-loader"], 53 | }, 54 | { 55 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 56 | type: "asset/resource", 57 | }, 58 | ], 59 | }, 60 | plugins: [ 61 | new CopyWebbackPlugin({ 62 | patterns: [ 63 | { from: "./Public/images/*.*", to: "images/[name][ext]" }, 64 | { from: "./Public/favicons/*.*", to: "[name][ext]" }, 65 | { from: "./Public/error.html", to: "error.leaf" }, 66 | { from: "./Public/robots.txt", to: "robots.txt" }, 67 | ], 68 | }), 69 | new HtmlWebpackPlugin({ 70 | chunks: ["index"], 71 | filename: "index.leaf", 72 | template: "./Public/index.html", 73 | }), 74 | new MiniCssExtractPlugin({ 75 | filename: "[name].[contenthash].css", 76 | }), 77 | ], 78 | }; 79 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const common = require("./webpack.common.js"); 3 | 4 | module.exports = merge(common, { 5 | mode: "development", 6 | devtool: "inline-source-map", 7 | }); 8 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const common = require("./webpack.common.js"); 3 | const BundleAnalyzerPlugin = 4 | require("webpack-bundle-analyzer").BundleAnalyzerPlugin; 5 | 6 | module.exports = merge(common, { 7 | mode: "production", 8 | devtool: "source-map", 9 | plugins: [ 10 | new BundleAnalyzerPlugin({ analyzerMode: "static", openAnalyzer: false }), 11 | ], 12 | }); 13 | --------------------------------------------------------------------------------