├── .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 |
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 |
98 |
99 |
129 |
130 |
131 |
132 |
667 |
668 |
669 |
686 |
687 |
688 |
689 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
703 |
708 |
709 |
710 |
711 |
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 |
15 |
16 | Experience our intuitive code editing environment:
17 |
18 |
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 |
--------------------------------------------------------------------------------