├── .gitignore ├── .justfile ├── .vscode ├── launch.json └── settings.json ├── Cargo.lock ├── Cargo.toml ├── Docs └── Notes.md ├── LICENSE ├── README.md └── src ├── main.rs └── swiftlint.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/rust 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust 3 | 4 | ### Rust ### 5 | # Generated by Cargo 6 | # will have compiled files and executables 7 | debug/ 8 | target/ 9 | 10 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 11 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 12 | Cargo.lock 13 | 14 | # These are backup files generated by rustfmt 15 | **/*.rs.bk 16 | 17 | # MSVC Windows builds of rustc generate these, which store debugging information 18 | *.pdb 19 | 20 | # End of https://www.toptal.com/developers/gitignore/api/rust 21 | -------------------------------------------------------------------------------- /.justfile: -------------------------------------------------------------------------------- 1 | set shell := ["fish", "-c"] 2 | 3 | _default: _list 4 | 5 | _list: 6 | just --list 7 | 8 | publish: 9 | #!/usr/bin/env fish 10 | set CURRENT_BRANCH (git symbolic-ref --short HEAD) 11 | if [ $CURRENT_BRANCH != main ] 12 | echo "Not on main branch. Please switch to main before publishing." 13 | exit 1 14 | end 15 | set NEXT_VERSION (just _next-version) 16 | gum confirm "Confirm next version: '$NEXT_VERSION'?"; or exit 1 17 | just _check-repo; or exit 1 18 | cargo clippy --fix 19 | just _check-repo; or exit 1 20 | cargo test; or exit 1 21 | echo "Updating Cargo.toml to version $NEXT_VERSION" 22 | toml set Cargo.toml package.version $NEXT_VERSION| sponge Cargo.toml 23 | gum confirm "git commit -a"; and git commit -a 24 | gum confirm "git tag?"; and git tag $NEXT_VERSION 25 | gum confirm "git push?"; and git push --tags origin main 26 | gum confirm "Rust publish"; and cargo publish 27 | 28 | gum confirm "Update homebrew?"; and just homebrew-release $NEXT_VERSION 29 | 30 | _check-repo: 31 | #!/usr/bin/env fish 32 | set is_dirty (git status --porcelain) 33 | if test -n "$is_dirty" 34 | echo "Repo is dirty. Please commit all changes before publishing." 35 | exit 1 36 | end 37 | 38 | _next-version: 39 | #!/usr/bin/env fish 40 | set LATEST_TAG (git describe --tags --abbrev=0) 41 | set PARTS (string split . $LATEST_TAG) 42 | set MAJOR $PARTS[1] 43 | set MINOR $PARTS[2] 44 | set PATCH $PARTS[3] 45 | set NEXT_PATCH (math $PATCH + 1) 46 | echo "$MAJOR.$MINOR.$NEXT_PATCH" 47 | 48 | homebrew-release VERSION: 49 | #!/usr/bin/env fish 50 | # https://federicoterzi.com/blog/how-to-publish-your-rust-project-on-homebrew/ 51 | 52 | set PROJECT_NAME "swiftlint-autodetect" 53 | set VERSION {{VERSION}} 54 | echo $VERSION 55 | 56 | # Build release, create tarball and calculate sha256 57 | cargo build --release 58 | pushd target/release/ 59 | tar -czf $PROJECT_NAME.tar.gz $PROJECT_NAME 60 | set SHA (shasum -a 256 $PROJECT_NAME.tar.gz | cut -d " " -f 1) 61 | echo $SHA 62 | popd 63 | 64 | # Create release on GitHub 65 | gh release create $VERSION target/release/$PROJECT_NAME.tar.gz --title "$PROJECT_NAME $VERSION" 66 | 67 | # Update homebrew formula 68 | pushd $HOME/Projects/homebrew-schwa 69 | git pull 70 | sed -i '' -e "s/sha256 \".*\"/sha256 \"$SHA\"/g" Formula/$PROJECT_NAME.rb 71 | sed -i '' -e "s/version \".*\"/version \"$VERSION\"/g" Formula/$PROJECT_NAME.rb 72 | git commit --all --message "$PROJECT_NAME $VERSION" 73 | git push 74 | popd 75 | 76 | test-homebrew: 77 | brew tap schwa/schwa 78 | brew update 79 | brew install swiftlint-autodetect 80 | 81 | test-everything: 82 | cargo run -- generate ~/Projects/Everything 83 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black", 3 | "python.pythonPath": "/Users/schwa/Library/Caches/pypoetry/virtualenvs/swiftlint-autodetect-7VpCUtgN-py3.9/bin/python", 4 | "cSpell.words": [ 5 | "analyzer" 6 | ], 7 | "cSpell.enabled": false 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "allocator-api2" 28 | version = "0.2.18" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 31 | 32 | [[package]] 33 | name = "anstream" 34 | version = "0.6.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 37 | dependencies = [ 38 | "anstyle", 39 | "anstyle-parse", 40 | "anstyle-query", 41 | "anstyle-wincon", 42 | "colorchoice", 43 | "is_terminal_polyfill", 44 | "utf8parse", 45 | ] 46 | 47 | [[package]] 48 | name = "anstyle" 49 | version = "1.0.7" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 52 | 53 | [[package]] 54 | name = "anstyle-parse" 55 | version = "0.2.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 58 | dependencies = [ 59 | "utf8parse", 60 | ] 61 | 62 | [[package]] 63 | name = "anstyle-query" 64 | version = "1.1.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 67 | dependencies = [ 68 | "windows-sys 0.52.0", 69 | ] 70 | 71 | [[package]] 72 | name = "anstyle-wincon" 73 | version = "3.0.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 76 | dependencies = [ 77 | "anstyle", 78 | "windows-sys 0.52.0", 79 | ] 80 | 81 | [[package]] 82 | name = "anyhow" 83 | version = "1.0.86" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 86 | 87 | [[package]] 88 | name = "arraydeque" 89 | version = "0.5.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" 92 | 93 | [[package]] 94 | name = "bitflags" 95 | version = "2.6.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 98 | 99 | [[package]] 100 | name = "cfg-if" 101 | version = "1.0.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 104 | 105 | [[package]] 106 | name = "clap" 107 | version = "4.5.8" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" 110 | dependencies = [ 111 | "clap_builder", 112 | "clap_derive", 113 | ] 114 | 115 | [[package]] 116 | name = "clap_builder" 117 | version = "4.5.8" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" 120 | dependencies = [ 121 | "anstream", 122 | "anstyle", 123 | "clap_lex", 124 | "strsim", 125 | ] 126 | 127 | [[package]] 128 | name = "clap_derive" 129 | version = "4.5.8" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" 132 | dependencies = [ 133 | "heck", 134 | "proc-macro2", 135 | "quote", 136 | "syn", 137 | ] 138 | 139 | [[package]] 140 | name = "clap_lex" 141 | version = "0.7.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 144 | 145 | [[package]] 146 | name = "colorchoice" 147 | version = "1.0.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 150 | 151 | [[package]] 152 | name = "colored" 153 | version = "2.1.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 156 | dependencies = [ 157 | "lazy_static", 158 | "windows-sys 0.48.0", 159 | ] 160 | 161 | [[package]] 162 | name = "colored_markup" 163 | version = "0.1.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "957f60a0329d7aff2b8557e7f4966c0014a1e19a9a35acb79df279500d4ba257" 166 | dependencies = [ 167 | "anyhow", 168 | "colored", 169 | "lazy_static", 170 | "nom", 171 | "regex", 172 | ] 173 | 174 | [[package]] 175 | name = "dirs" 176 | version = "5.0.1" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 179 | dependencies = [ 180 | "dirs-sys", 181 | ] 182 | 183 | [[package]] 184 | name = "dirs-sys" 185 | version = "0.4.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 188 | dependencies = [ 189 | "libc", 190 | "option-ext", 191 | "redox_users", 192 | "windows-sys 0.48.0", 193 | ] 194 | 195 | [[package]] 196 | name = "encoding_rs" 197 | version = "0.8.34" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 200 | dependencies = [ 201 | "cfg-if", 202 | ] 203 | 204 | [[package]] 205 | name = "equivalent" 206 | version = "1.0.1" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 209 | 210 | [[package]] 211 | name = "errno" 212 | version = "0.3.9" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 215 | dependencies = [ 216 | "libc", 217 | "windows-sys 0.52.0", 218 | ] 219 | 220 | [[package]] 221 | name = "fastrand" 222 | version = "2.1.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 225 | 226 | [[package]] 227 | name = "getrandom" 228 | version = "0.2.15" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 231 | dependencies = [ 232 | "cfg-if", 233 | "libc", 234 | "wasi", 235 | ] 236 | 237 | [[package]] 238 | name = "hashbrown" 239 | version = "0.14.5" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 242 | dependencies = [ 243 | "ahash", 244 | "allocator-api2", 245 | ] 246 | 247 | [[package]] 248 | name = "hashlink" 249 | version = "0.8.4" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" 252 | dependencies = [ 253 | "hashbrown", 254 | ] 255 | 256 | [[package]] 257 | name = "heck" 258 | version = "0.5.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 261 | 262 | [[package]] 263 | name = "indexmap" 264 | version = "2.2.6" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 267 | dependencies = [ 268 | "equivalent", 269 | "hashbrown", 270 | ] 271 | 272 | [[package]] 273 | name = "is_terminal_polyfill" 274 | version = "1.70.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 277 | 278 | [[package]] 279 | name = "itoa" 280 | version = "1.0.11" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 283 | 284 | [[package]] 285 | name = "lazy_static" 286 | version = "1.5.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 289 | 290 | [[package]] 291 | name = "libc" 292 | version = "0.2.155" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 295 | 296 | [[package]] 297 | name = "libredox" 298 | version = "0.1.3" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 301 | dependencies = [ 302 | "bitflags", 303 | "libc", 304 | ] 305 | 306 | [[package]] 307 | name = "libyml" 308 | version = "0.0.3" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "3e281a65eeba3d4503a2839252f86374528f9ceafe6fed97c1d3b52e1fb625c1" 311 | 312 | [[package]] 313 | name = "linux-raw-sys" 314 | version = "0.4.14" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 317 | 318 | [[package]] 319 | name = "log" 320 | version = "0.4.22" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 323 | 324 | [[package]] 325 | name = "memchr" 326 | version = "2.7.4" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 329 | 330 | [[package]] 331 | name = "minimal-lexical" 332 | version = "0.2.1" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 335 | 336 | [[package]] 337 | name = "nom" 338 | version = "7.1.3" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 341 | dependencies = [ 342 | "memchr", 343 | "minimal-lexical", 344 | ] 345 | 346 | [[package]] 347 | name = "once_cell" 348 | version = "1.19.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 351 | 352 | [[package]] 353 | name = "option-ext" 354 | version = "0.2.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 357 | 358 | [[package]] 359 | name = "proc-macro2" 360 | version = "1.0.86" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 363 | dependencies = [ 364 | "unicode-ident", 365 | ] 366 | 367 | [[package]] 368 | name = "quote" 369 | version = "1.0.36" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 372 | dependencies = [ 373 | "proc-macro2", 374 | ] 375 | 376 | [[package]] 377 | name = "redox_users" 378 | version = "0.4.5" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" 381 | dependencies = [ 382 | "getrandom", 383 | "libredox", 384 | "thiserror", 385 | ] 386 | 387 | [[package]] 388 | name = "regex" 389 | version = "1.10.5" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 392 | dependencies = [ 393 | "aho-corasick", 394 | "memchr", 395 | "regex-automata", 396 | "regex-syntax", 397 | ] 398 | 399 | [[package]] 400 | name = "regex-automata" 401 | version = "0.4.7" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 404 | dependencies = [ 405 | "aho-corasick", 406 | "memchr", 407 | "regex-syntax", 408 | ] 409 | 410 | [[package]] 411 | name = "regex-syntax" 412 | version = "0.8.4" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 415 | 416 | [[package]] 417 | name = "rustix" 418 | version = "0.38.34" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 421 | dependencies = [ 422 | "bitflags", 423 | "errno", 424 | "libc", 425 | "linux-raw-sys", 426 | "windows-sys 0.52.0", 427 | ] 428 | 429 | [[package]] 430 | name = "ryu" 431 | version = "1.0.18" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 434 | 435 | [[package]] 436 | name = "serde" 437 | version = "1.0.203" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 440 | dependencies = [ 441 | "serde_derive", 442 | ] 443 | 444 | [[package]] 445 | name = "serde_derive" 446 | version = "1.0.203" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 449 | dependencies = [ 450 | "proc-macro2", 451 | "quote", 452 | "syn", 453 | ] 454 | 455 | [[package]] 456 | name = "serde_json" 457 | version = "1.0.118" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" 460 | dependencies = [ 461 | "itoa", 462 | "ryu", 463 | "serde", 464 | ] 465 | 466 | [[package]] 467 | name = "serde_spanned" 468 | version = "0.6.6" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" 471 | dependencies = [ 472 | "serde", 473 | ] 474 | 475 | [[package]] 476 | name = "serde_yml" 477 | version = "0.0.10" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f" 480 | dependencies = [ 481 | "indexmap", 482 | "itoa", 483 | "libyml", 484 | "log", 485 | "memchr", 486 | "ryu", 487 | "serde", 488 | "serde_json", 489 | "tempfile", 490 | ] 491 | 492 | [[package]] 493 | name = "shellexpand" 494 | version = "3.1.0" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" 497 | dependencies = [ 498 | "dirs", 499 | ] 500 | 501 | [[package]] 502 | name = "strsim" 503 | version = "0.11.1" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 506 | 507 | [[package]] 508 | name = "swiftlint-autodetect" 509 | version = "0.1.4" 510 | dependencies = [ 511 | "anyhow", 512 | "clap", 513 | "colored_markup", 514 | "hashlink", 515 | "regex", 516 | "serde", 517 | "serde_yml", 518 | "shellexpand", 519 | "tempfile", 520 | "toml", 521 | "yaml-rust2", 522 | ] 523 | 524 | [[package]] 525 | name = "syn" 526 | version = "2.0.68" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" 529 | dependencies = [ 530 | "proc-macro2", 531 | "quote", 532 | "unicode-ident", 533 | ] 534 | 535 | [[package]] 536 | name = "tempfile" 537 | version = "3.10.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 540 | dependencies = [ 541 | "cfg-if", 542 | "fastrand", 543 | "rustix", 544 | "windows-sys 0.52.0", 545 | ] 546 | 547 | [[package]] 548 | name = "thiserror" 549 | version = "1.0.61" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 552 | dependencies = [ 553 | "thiserror-impl", 554 | ] 555 | 556 | [[package]] 557 | name = "thiserror-impl" 558 | version = "1.0.61" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 561 | dependencies = [ 562 | "proc-macro2", 563 | "quote", 564 | "syn", 565 | ] 566 | 567 | [[package]] 568 | name = "toml" 569 | version = "0.8.14" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" 572 | dependencies = [ 573 | "serde", 574 | "serde_spanned", 575 | "toml_datetime", 576 | "toml_edit", 577 | ] 578 | 579 | [[package]] 580 | name = "toml_datetime" 581 | version = "0.6.6" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" 584 | dependencies = [ 585 | "serde", 586 | ] 587 | 588 | [[package]] 589 | name = "toml_edit" 590 | version = "0.22.14" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" 593 | dependencies = [ 594 | "indexmap", 595 | "serde", 596 | "serde_spanned", 597 | "toml_datetime", 598 | "winnow", 599 | ] 600 | 601 | [[package]] 602 | name = "unicode-ident" 603 | version = "1.0.12" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 606 | 607 | [[package]] 608 | name = "utf8parse" 609 | version = "0.2.2" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 612 | 613 | [[package]] 614 | name = "version_check" 615 | version = "0.9.4" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 618 | 619 | [[package]] 620 | name = "wasi" 621 | version = "0.11.0+wasi-snapshot-preview1" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 624 | 625 | [[package]] 626 | name = "windows-sys" 627 | version = "0.48.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 630 | dependencies = [ 631 | "windows-targets 0.48.5", 632 | ] 633 | 634 | [[package]] 635 | name = "windows-sys" 636 | version = "0.52.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 639 | dependencies = [ 640 | "windows-targets 0.52.5", 641 | ] 642 | 643 | [[package]] 644 | name = "windows-targets" 645 | version = "0.48.5" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 648 | dependencies = [ 649 | "windows_aarch64_gnullvm 0.48.5", 650 | "windows_aarch64_msvc 0.48.5", 651 | "windows_i686_gnu 0.48.5", 652 | "windows_i686_msvc 0.48.5", 653 | "windows_x86_64_gnu 0.48.5", 654 | "windows_x86_64_gnullvm 0.48.5", 655 | "windows_x86_64_msvc 0.48.5", 656 | ] 657 | 658 | [[package]] 659 | name = "windows-targets" 660 | version = "0.52.5" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 663 | dependencies = [ 664 | "windows_aarch64_gnullvm 0.52.5", 665 | "windows_aarch64_msvc 0.52.5", 666 | "windows_i686_gnu 0.52.5", 667 | "windows_i686_gnullvm", 668 | "windows_i686_msvc 0.52.5", 669 | "windows_x86_64_gnu 0.52.5", 670 | "windows_x86_64_gnullvm 0.52.5", 671 | "windows_x86_64_msvc 0.52.5", 672 | ] 673 | 674 | [[package]] 675 | name = "windows_aarch64_gnullvm" 676 | version = "0.48.5" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 679 | 680 | [[package]] 681 | name = "windows_aarch64_gnullvm" 682 | version = "0.52.5" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 685 | 686 | [[package]] 687 | name = "windows_aarch64_msvc" 688 | version = "0.48.5" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 691 | 692 | [[package]] 693 | name = "windows_aarch64_msvc" 694 | version = "0.52.5" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 697 | 698 | [[package]] 699 | name = "windows_i686_gnu" 700 | version = "0.48.5" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 703 | 704 | [[package]] 705 | name = "windows_i686_gnu" 706 | version = "0.52.5" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 709 | 710 | [[package]] 711 | name = "windows_i686_gnullvm" 712 | version = "0.52.5" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 715 | 716 | [[package]] 717 | name = "windows_i686_msvc" 718 | version = "0.48.5" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 721 | 722 | [[package]] 723 | name = "windows_i686_msvc" 724 | version = "0.52.5" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 727 | 728 | [[package]] 729 | name = "windows_x86_64_gnu" 730 | version = "0.48.5" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 733 | 734 | [[package]] 735 | name = "windows_x86_64_gnu" 736 | version = "0.52.5" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 739 | 740 | [[package]] 741 | name = "windows_x86_64_gnullvm" 742 | version = "0.48.5" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 745 | 746 | [[package]] 747 | name = "windows_x86_64_gnullvm" 748 | version = "0.52.5" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 751 | 752 | [[package]] 753 | name = "windows_x86_64_msvc" 754 | version = "0.48.5" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 757 | 758 | [[package]] 759 | name = "windows_x86_64_msvc" 760 | version = "0.52.5" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 763 | 764 | [[package]] 765 | name = "winnow" 766 | version = "0.6.13" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" 769 | dependencies = [ 770 | "memchr", 771 | ] 772 | 773 | [[package]] 774 | name = "yaml-rust2" 775 | version = "0.8.1" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" 778 | dependencies = [ 779 | "arraydeque", 780 | "encoding_rs", 781 | "hashlink", 782 | ] 783 | 784 | [[package]] 785 | name = "zerocopy" 786 | version = "0.7.34" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 789 | dependencies = [ 790 | "zerocopy-derive", 791 | ] 792 | 793 | [[package]] 794 | name = "zerocopy-derive" 795 | version = "0.7.34" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 798 | dependencies = [ 799 | "proc-macro2", 800 | "quote", 801 | "syn", 802 | ] 803 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swiftlint-autodetect" 3 | version = "0.1.4" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Automatically generate Swiftlint rules from your codebase." 7 | url = "https://github.com/schwa/swiftlint-autodetect" 8 | repository = "https://github.com/schwa/swiftlint-autodetect" 9 | 10 | [dependencies] 11 | anyhow = "1.0.86" 12 | clap = { version = "4.5.8", features = ["derive"] } 13 | colored_markup = "0.1.1" 14 | hashlink = "0.8.4" 15 | regex = "1.10.5" 16 | serde = { version = "1.0.203", features = ["derive"] } 17 | serde_yml = "0.0.10" 18 | shellexpand = "3.1.0" 19 | tempfile = "3.10.1" 20 | toml = "0.8.14" 21 | yaml-rust2 = "0.8.1" 22 | -------------------------------------------------------------------------------- /Docs/Notes.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## swiftlint help 4 | 5 | ```plaintext 6 | $ swiftlint lint --help 7 | OVERVIEW: Print lint warnings and errors 8 | 9 | USAGE: swiftlint lint [] [ ...] 10 | 11 | ARGUMENTS: 12 | List of paths to the files or directories to lint. 13 | 14 | OPTIONS: 15 | --config The path to one or more SwiftLint configuration files, evaluated as a 16 | parent-child hierarchy. 17 | --fix, --autocorrect Correct violations whenever possible. 18 | --format Should reformat the Swift files using the same mechanism used by Xcode (via 19 | SourceKit). 20 | Only applied with `--fix`/`--autocorrect`. 21 | --use-alternative-excluding 22 | Use an alternative algorithm to exclude paths for `excluded`, which may be 23 | faster in some cases. 24 | --use-script-input-files 25 | Read SCRIPT_INPUT_FILE* environment variables as files. 26 | --strict Upgrades warnings to serious violations (errors). 27 | --lenient Downgrades serious violations to warnings, warning threshold is disabled. 28 | --force-exclude Exclude files in config `excluded` even if their paths are explicitly 29 | specified. 30 | --benchmark Save benchmarks to `benchmark_files.txt` and `benchmark_rules.txt`. 31 | --reporter The reporter used to log errors and warnings. 32 | --baseline The path to a baseline file, which will be used to filter out detected 33 | violations. 34 | --write-baseline 35 | The path to save detected violations to as a new baseline. 36 | --in-process-sourcekit Use the in-process version of SourceKit. 37 | --output The file where violations should be saved. Prints to stdout by default. 38 | --progress Show a live-updating progress bar instead of each file being processed. 39 | --use-stdin Lint standard input. 40 | --quiet Don't print status logs like 'Linting ' & 'Done linting'. 41 | --silence-deprecation-warnings 42 | Don't print deprecation warnings. 43 | --cache-path 44 | The directory of the cache used when linting. 45 | --no-cache Ignore cache when linting. 46 | --enable-all-rules Run all rules, even opt-in and disabled ones, ignoring `only_rules`. 47 | --version Show the version. 48 | -h, --help Show help information. 49 | ``` 50 | 51 | ## Swiftlint Configuration 52 | 53 | ```plaintext 54 | # By default, SwiftLint uses a set of sensible default rules you can adjust: 55 | disabled_rules: # rule identifiers turned on by default to exclude from running 56 | - colon 57 | - comma 58 | - control_statement 59 | opt_in_rules: # some rules are turned off by default, so you need to opt-in 60 | - empty_count # find all the available rules by running: `swiftlint rules` 61 | 62 | # Alternatively, specify all rules explicitly by uncommenting this option: 63 | # only_rules: # delete `disabled_rules` & `opt_in_rules` if using this 64 | # - empty_parameters 65 | # - vertical_whitespace 66 | 67 | analyzer_rules: # rules run by `swiftlint analyze` 68 | - explicit_self 69 | 70 | # Case-sensitive paths to include during linting. Directory paths supplied on the 71 | # command line will be ignored. 72 | included: 73 | - Sources 74 | excluded: # case-sensitive paths to ignore during linting. Takes precedence over `included` 75 | - Carthage 76 | - Pods 77 | - Sources/ExcludedFolder 78 | - Sources/ExcludedFile.swift 79 | - Sources/*/ExcludedFile.swift # exclude files with a wildcard 80 | 81 | # If true, SwiftLint will not fail if no lintable files are found. 82 | allow_zero_lintable_files: false 83 | 84 | # If true, SwiftLint will treat all warnings as errors. 85 | strict: false 86 | 87 | # The path to a baseline file, which will be used to filter out detected violations. 88 | baseline: Baseline.json 89 | 90 | # The path to save detected violations to as a new baseline. 91 | write_baseline: Baseline.json 92 | 93 | # configurable rules can be customized from this configuration file 94 | # binary rules can set their severity level 95 | force_cast: warning # implicitly 96 | force_try: 97 | severity: warning # explicitly 98 | # rules that have both warning and error levels, can set just the warning level 99 | # implicitly 100 | line_length: 110 101 | # they can set both implicitly with an array 102 | type_body_length: 103 | - 300 # warning 104 | - 400 # error 105 | # or they can set both explicitly 106 | file_length: 107 | warning: 500 108 | error: 1200 109 | # naming rules can set warnings/errors for min_length and max_length 110 | # additionally they can set excluded names 111 | type_name: 112 | min_length: 4 # only warning 113 | max_length: # warning and error 114 | warning: 40 115 | error: 50 116 | excluded: iPhone # excluded via string 117 | allowed_symbols: ["_"] # these are allowed in type names 118 | identifier_name: 119 | min_length: # only min_length 120 | error: 4 # only error 121 | excluded: # excluded via string array 122 | - id 123 | - URL 124 | - GlobalAPIKey 125 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging, summary) 126 | ``` 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jonathan Wight 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftlint-autodetect 2 | 3 | Rust-based tool to autodetect a base set of swiftlint rules. 4 | 5 | ## Installation 6 | 7 | Via homebrew: 8 | 9 | ```sh 10 | brew tap schwa/schwa 11 | brew install swiftlint-autodetect 12 | ``` 13 | 14 | Or directly via cargo: 15 | 16 | ```sh 17 | cargo install --git https://github.com/schwa/swiftlint-autodetect 18 | ``` 19 | 20 | To run `swiftlint-autodetect`, [swiftlint](https://github.com/realm/SwiftLint) must be installed and on your path. 21 | 22 | ## Usage 23 | 24 | Casey Liss wrote up a good description of how to use swiftlint-autodetect here: . This write-up is based on the previous Python version of the tool, but the usage is similar. 25 | 26 | ```sh 27 | # Count the number of violations of _all_ SwiftLint rules in the given directory 28 | $ swiftlint-autodetect count 29 | # Output a SwiftLint configuration that disables all rules that are violated in the given directory. 30 | $ swiftlint-autodetect generate --count 31 | # Save a SwiftLint configuration that disables all rules that are violated in the given directory. 32 | $ swiftlint-autodetect generate --count --output .swiftlint.yml 33 | ``` 34 | 35 | ## Help 36 | 37 | ```plaintext 38 | $ swiftlint-autodetect generate --help 39 | Generate a SwiftLint configuration, by disabling rules with a minimum number of violations 40 | 41 | Usage: swiftlint-autodetect generate [OPTIONS] [PATH] 42 | 43 | Arguments: 44 | [PATH] Path to the project [default: .] 45 | 46 | Options: 47 | -c, --counts 48 | Include violation counts in the generated configuration 49 | -o, --output 50 | Output path for the generated configuration 51 | -m, --minimum-violations 52 | Minimum number of violations required to disable a rule [default: 1] 53 | -i, --ignore-fixable 54 | Don't disable fixable rules 55 | -h, --help 56 | Print help 57 | ``` 58 | 59 | ## Counting Violations 60 | 61 | To show an ordered list of rules, and the number of violations per rule use the `count` subcommand. 62 | 63 | This subcommand also highlights rules that can be corrected with `swiftlint --fix` 64 | 65 | ```sh 66 | $ swiftlint-autodetect count ~/Projects/Demos 67 | explicit_acl: 2182 68 | explicit_type_interface: 1669 69 | identifier_name: 622 70 | missing_docs: 409 71 | type_contents_order: 335 72 | explicit_top_level_acl: 321 73 | implicit_return (*): 306 74 | let_var_whitespace: 295 75 | line_length: 294 76 | force_unwrapping: 260 77 | vertical_whitespace_between_cases (*): 167 78 | nesting: 165 79 | orphaned_doc_comment: 140 80 | file_types_order: 140 81 | indentation_width: 134 82 | required_deinit: 117 83 | trailing_comma (*): 106 84 | todo: 106 85 | ``` 86 | 87 | ## Configuration 88 | 89 | Save a file at `~/.config/swiftlint-autodetect/config.toml`. Currently supported keys are 'always_disabled_rules' to specify rules that always disabled for all projects regardless of violation count, e.g.: 90 | 91 | ```toml 92 | always_disabled_rules = ["yoda_condition"] 93 | ``` 94 | 95 | ## How this works 96 | 97 | swiftlint-autodetect queries swiftlint for the full list of rules and creates a temporary swiftlint config file enabling all these rules. It then performs a lint operation on the source code at the path specified and finds out which rules would be violated. It then outputs a configuration disabling the violated rules. 98 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use clap::Subcommand; 4 | use std; 5 | use std::path; 6 | use std::path::PathBuf; 7 | 8 | mod swiftlint; 9 | use swiftlint::Swiftlint; 10 | 11 | #[derive(Parser)] 12 | #[command(version, about, long_about = None)] 13 | struct Cli { 14 | #[command(subcommand)] 15 | command: Option, 16 | } 17 | 18 | #[derive(Subcommand)] 19 | enum Commands { 20 | /// Count the number of SwiftLint rule violations in a project. 21 | Count { 22 | #[arg(default_value = ".")] 23 | path: PathBuf, 24 | }, 25 | /// Generate a SwiftLint configuration, by disabling rules with a minimum number of violations. 26 | Generate { 27 | /// Path to the project. 28 | #[arg(default_value = ".")] 29 | path: PathBuf, 30 | 31 | /// Include violation counts in the generated configuration. 32 | #[clap(long = "counts", short = 'c')] 33 | include_counts: bool, 34 | 35 | /// Output path for the generated configuration. 36 | #[clap(long, short)] 37 | output: Option, 38 | 39 | /// Minimum number of violations required to disable a rule. 40 | #[clap(long, short, default_value = "1")] 41 | minimum_violations: u32, 42 | 43 | /// Don't disable fixable rules. 44 | #[clap(long, short)] 45 | ignore_fixable: bool, 46 | }, 47 | } 48 | 49 | fn main() -> Result<()> { 50 | let cli = Cli::parse(); 51 | 52 | match cli.command { 53 | Some(Commands::Count { path }) => { 54 | let path: PathBuf = path::absolute(path)?; 55 | let swiftlint = Swiftlint::new(path)?; 56 | swiftlint.count()?; 57 | } 58 | Some(Commands::Generate { 59 | path, 60 | include_counts, 61 | output, 62 | minimum_violations, 63 | ignore_fixable, 64 | }) => { 65 | let path: PathBuf = path::absolute(path)?; 66 | let swiftlint = Swiftlint::new(path)?; 67 | swiftlint.generate(output, include_counts, minimum_violations, ignore_fixable)?; 68 | } 69 | None => { 70 | println!("No command provided"); 71 | } 72 | } 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /src/swiftlint.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use colored_markup::{println_markup, StyleSheet}; 3 | use hashlink::LinkedHashMap; 4 | use regex::Regex; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | use std::fs; 8 | use std::io::Write; 9 | use std::path::{Path, PathBuf}; 10 | use std::process::Command; 11 | use yaml_rust2::{yaml, Yaml, YamlEmitter, YamlLoader}; 12 | 13 | #[derive(Debug, Deserialize)] 14 | struct Config { 15 | always_disabled_rules: Option>, 16 | } 17 | 18 | impl Config { 19 | fn new() -> Self { 20 | Config { 21 | always_disabled_rules: None, 22 | } 23 | } 24 | 25 | fn load() -> Result { 26 | let path = "~/.config/swiftlint-autodetect/config.toml"; 27 | let path = shellexpand::tilde(path).into_owned(); 28 | // if path exists 29 | if Path::new(&path).exists() { 30 | let contents = fs::read_to_string(&path)?; 31 | let config: Config = toml::from_str(&contents)?; 32 | return Ok(config); 33 | } 34 | Ok(Config::new()) 35 | } 36 | } 37 | 38 | #[derive(Debug)] 39 | pub struct Swiftlint { 40 | pub binary_path: PathBuf, 41 | pub working_directory: PathBuf, 42 | pub rules: Vec, 43 | } 44 | 45 | #[allow(dead_code)] 46 | #[derive(Debug)] 47 | pub struct Rule { 48 | pub identifier: String, 49 | pub opt_in: bool, 50 | pub correctable: bool, 51 | pub kind: String, 52 | pub analyzer: bool, 53 | pub uses_sourcekit: bool, 54 | } 55 | 56 | #[derive(Debug, Serialize)] 57 | pub struct SwiftLintConfig { 58 | pub disabled_rules: Vec, 59 | pub excluded: Vec, 60 | pub opt_in_rules: Vec, 61 | pub only_rules: Vec, 62 | } 63 | 64 | #[allow(dead_code)] 65 | #[derive(Debug)] 66 | pub struct Diagnostic { 67 | pub file: PathBuf, 68 | pub line: u32, 69 | pub character: u32, 70 | pub severity: String, // TODO: Make into an enu, 71 | pub reason: String, 72 | pub rule_id: String, 73 | } 74 | 75 | impl From<&str> for Diagnostic { 76 | fn from(line: &str) -> Self { 77 | // TODO: use --reporter=json instead. 78 | let pattern = Regex::new(r"^(?.+):(?\d+):(?\d+): (?warning|error): (?.+) \((?.+)\)$").unwrap(); 79 | let captures = pattern.captures(line).unwrap(); 80 | let path = PathBuf::from(captures.name("path").unwrap().as_str()); 81 | let line = captures 82 | .name("line") 83 | .unwrap() 84 | .as_str() 85 | .parse::() 86 | .unwrap(); 87 | let column = captures 88 | .name("column") 89 | .unwrap() 90 | .as_str() 91 | .parse::() 92 | .unwrap(); 93 | let level = captures.name("level").unwrap().as_str().to_string(); 94 | let message = captures.name("description").unwrap().as_str().to_string(); 95 | let identifier = captures.name("identifier").unwrap().as_str().to_string(); 96 | Diagnostic { 97 | file: path, 98 | line, 99 | character: column, 100 | severity: level, 101 | reason: message, 102 | rule_id: identifier, 103 | } 104 | } 105 | } 106 | 107 | impl Swiftlint { 108 | pub fn new(working_directory: PathBuf) -> Result { 109 | let output = Command::new("/usr/bin/which") 110 | .arg("swiftlint") 111 | .output() 112 | .expect("failed to execute process"); 113 | 114 | let binary_path = String::from_utf8(output.stdout)?.trim().to_string(); 115 | let binary_path = PathBuf::from(binary_path); 116 | 117 | let mut swift_lint = Swiftlint { 118 | binary_path, 119 | working_directory, 120 | rules: Vec::new(), 121 | }; 122 | swift_lint.discover_rules()?; 123 | Ok(swift_lint) 124 | } 125 | 126 | pub fn discover_rules(&mut self) -> Result<()> { 127 | let stdout = Command::new(&self.binary_path) 128 | .arg("rules") 129 | .current_dir(&self.working_directory) 130 | .output()? 131 | .stdout; 132 | 133 | let stdout = String::from_utf8(stdout)?; 134 | let lines = stdout.lines(); 135 | 136 | // Skip first line 137 | let mut lines = lines.skip(1); 138 | // Parse header 139 | let header = lines.next().unwrap(); 140 | let header_parts: Vec<&str> = header.split('|').map(|part| part.trim()).collect(); 141 | 142 | let index_for_identifier = header_parts 143 | .iter() 144 | .position(|&part| part == "identifier") 145 | .unwrap(); 146 | let index_for_opt_in = header_parts 147 | .iter() 148 | .position(|&part| part == "opt-in") 149 | .unwrap(); 150 | let index_for_correctable = header_parts 151 | .iter() 152 | .position(|&part| part == "correctable") 153 | .unwrap(); 154 | let index_for_kind = header_parts 155 | .iter() 156 | .position(|&part| part == "kind") 157 | .unwrap(); 158 | let index_for_analyzer = header_parts 159 | .iter() 160 | .position(|&part| part == "analyzer") 161 | .unwrap(); 162 | let index_for_uses_sourcekit = header_parts 163 | .iter() 164 | .position(|&part| part == "uses sourcekit") 165 | .unwrap(); 166 | 167 | // Skip next line 168 | let _ = lines.next().unwrap(); 169 | 170 | // Trim off first three lines & last line 171 | let lines = lines.take_while(|line| *line != "+------------------------------------------+--------+-------------+------------------------+-------------+----------+----------------+---------------+"); 172 | let rules: Vec = lines 173 | .map(|line| { 174 | let parts: Vec<&str> = line.split('|').map(|part| part.trim()).collect(); 175 | Rule { 176 | identifier: parts[index_for_identifier].to_string(), 177 | opt_in: parts[index_for_opt_in] == "yes", 178 | correctable: parts[index_for_correctable] == "yes", 179 | kind: parts[index_for_kind].to_string(), 180 | analyzer: parts[index_for_analyzer] == "yes", 181 | uses_sourcekit: parts[index_for_uses_sourcekit] == "yes", 182 | } 183 | }) 184 | .collect(); 185 | self.rules = rules; 186 | Ok(()) 187 | } 188 | 189 | pub fn generate_config(&self) -> Result { 190 | let dotbuild = fs::canonicalize(&self.working_directory)?.join(".build/*"); 191 | 192 | let exclusions: Vec<&str> = vec![dotbuild.to_str().unwrap()]; 193 | 194 | // get all non-analyzer rules 195 | let rules = self 196 | .rules 197 | .iter() 198 | .filter(|rule| !rule.analyzer) 199 | .collect::>(); 200 | 201 | let config = SwiftLintConfig { 202 | disabled_rules: vec![], 203 | excluded: exclusions 204 | .iter() 205 | .map(|exclusion| exclusion.to_string()) 206 | .collect(), 207 | opt_in_rules: rules.iter().map(|rule| rule.identifier.clone()).collect(), 208 | only_rules: vec![], 209 | }; 210 | 211 | let yaml = serde_yml::to_string(&config)?; 212 | 213 | let path = PathBuf::from("/tmp/swiftlint.yml"); 214 | let mut file = std::fs::File::create(&path)?; 215 | 216 | file.write_all(yaml.as_bytes())?; 217 | Ok(path) 218 | } 219 | 220 | pub fn lint(&self, config_path: &PathBuf) -> Result> { 221 | let output = Command::new(&self.binary_path) 222 | .args(["lint", "--quiet", "--config", config_path.to_str().unwrap()]) 223 | .current_dir(&self.working_directory) 224 | .output()?; 225 | let stderr = String::from_utf8(output.stderr).unwrap(); 226 | if !stderr.is_empty() { 227 | eprintln!("{}", stderr); 228 | } 229 | 230 | let stdout = String::from_utf8(output.stdout).unwrap(); 231 | 232 | // write stdout to /tmp/swiftlint.log 233 | let mut file = fs::File::create("/tmp/swiftlint.log")?; 234 | file.write_all(stdout.as_bytes())?; 235 | 236 | let diagnostics: Vec = stdout.lines().map(Diagnostic::from).collect(); 237 | 238 | Ok(diagnostics) 239 | } 240 | 241 | pub fn count(&self) -> Result<()> { 242 | let style_sheet = StyleSheet::parse( 243 | " 244 | fixable { foreground: bright-green; styles: underline } 245 | warning { foreground: bright-yellow } 246 | bad { foreground: bright-red } 247 | ", 248 | ) 249 | .unwrap(); 250 | 251 | let path = self.generate_config().unwrap(); 252 | 253 | let diagnostics = self.lint(&path).unwrap(); 254 | 255 | // Count the diagnostics by identifier 256 | let mut diagnostics_by_identifier: HashMap = HashMap::new(); 257 | for diagnostic in diagnostics.iter() { 258 | let count = diagnostics_by_identifier 259 | .entry(diagnostic.rule_id.clone()) 260 | .or_insert(0); 261 | *count += 1; 262 | } 263 | 264 | // sort by most diagnostics 265 | let mut diagnostics_by_identifier: Vec<(String, u32)> = diagnostics_by_identifier 266 | .iter() 267 | .map(|(identifier, count)| (identifier.clone(), *count)) 268 | .collect(); 269 | 270 | diagnostics_by_identifier.sort_by(|a, b| b.1.cmp(&a.1)); 271 | 272 | for (identifier, count) in diagnostics_by_identifier.iter() { 273 | let mut line = String::new(); 274 | let rule = self 275 | .rules 276 | .iter() 277 | .find(|rule| rule.identifier == *identifier) 278 | .unwrap(); 279 | 280 | if *count >= 10 { 281 | line.push_str(format!("{}: {}", rule.identifier, count).as_str()); 282 | } else { 283 | line.push_str( 284 | format!("{}: {}", rule.identifier, count).as_str(), 285 | ); 286 | } 287 | if rule.correctable { 288 | line.push_str(" fixable"); 289 | } 290 | line.push_str(format!(" ({})", rule.kind).as_str()); 291 | 292 | println_markup!(&style_sheet, "{}", line); 293 | } 294 | 295 | Ok(()) 296 | } 297 | 298 | pub fn generate( 299 | &self, 300 | output_path: Option, 301 | include_counts: bool, 302 | minimum_violations: u32, 303 | ignore_fixable: bool, 304 | ) -> Result<()> { 305 | let app_config = Config::load()?; 306 | 307 | let path = self.generate_config()?; 308 | 309 | let diagnostics = self.lint(&path)?; 310 | let mut diagnostics_by_identifier: HashMap = HashMap::new(); 311 | for diagnostic in diagnostics.iter() { 312 | let count = diagnostics_by_identifier 313 | .entry(diagnostic.rule_id.clone()) 314 | .or_insert(0); 315 | *count += 1; 316 | } 317 | 318 | // Create a string buf to write to 319 | let mut output = String::new(); 320 | 321 | if let Some(output_path) = &output_path { 322 | // if output path exists 323 | if output_path.exists() { 324 | // modify the yaml 325 | let modified_yaml = modify_yaml(output_path, vec!["disabled_rules", "only_rules"])?; 326 | output.push_str(&modified_yaml); 327 | output.push('\n'); 328 | } 329 | } 330 | 331 | output.push_str("only_rules:\n"); 332 | for rule in self.rules.iter() { 333 | let count = diagnostics_by_identifier 334 | .get(&rule.identifier) 335 | .unwrap_or(&0); 336 | let mut line = format!(" - {}", rule.identifier); 337 | if include_counts && *count != 0 { 338 | line = format!("{} # {} violations", line, count); 339 | if rule.correctable { 340 | line = format!("{} (fixable)", line); 341 | } 342 | } 343 | let mut disabled = false; 344 | if let Some(always_disabled_rules) = &app_config.always_disabled_rules { 345 | if always_disabled_rules.contains(&rule.identifier) { 346 | disabled = true; 347 | } 348 | } 349 | if *count >= minimum_violations && !(ignore_fixable && rule.correctable) { 350 | disabled = true 351 | } 352 | 353 | if disabled { 354 | line = format!("#{}", line); 355 | } 356 | 357 | output.push_str(format!("{}\n", &line).as_str()); 358 | } 359 | 360 | if let Some(output_path) = &output_path { 361 | let mut file = fs::File::create(output_path)?; 362 | file.write_all(output.as_bytes())?; 363 | } else { 364 | // write to stdout 365 | println!("{}", output); 366 | } 367 | 368 | Ok(()) 369 | } 370 | } 371 | 372 | #[allow(dead_code)] 373 | pub fn modify_yaml(path: &Path, keys_to_strip: Vec<&str>) -> Result { 374 | let contents = fs::read_to_string(path)?; 375 | let docs = YamlLoader::load_from_str(&contents)?; 376 | let doc = &docs[0]; 377 | 378 | let original = doc.as_hash().unwrap(); 379 | let mut hash = LinkedHashMap::::new(); 380 | 381 | for (key, value) in original { 382 | let key_str = key.as_str().unwrap(); 383 | if keys_to_strip.contains(&key_str) { 384 | continue; 385 | } 386 | hash.insert(key.clone(), value.clone()); 387 | } 388 | 389 | let mut output = String::new(); 390 | 391 | let new_yaml = yaml::Yaml::Hash(hash); 392 | 393 | let mut emitter = YamlEmitter::new(&mut output); 394 | 395 | emitter.dump(&new_yaml).unwrap(); // dump the YAML object to a String 396 | 397 | Ok(output) 398 | } 399 | --------------------------------------------------------------------------------