From 3bf95d46a9c29d7913e98e63916b4c24f935793e Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:07:20 -0400 Subject: [PATCH 01/35] feat: add `ui_test` dependency --- Cargo.lock | 438 +++++++++++++++++++++++++++++-------------- bevy_lint/Cargo.toml | 7 + 2 files changed, 306 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea9ad28f..e5f93fa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,18 +10,18 @@ checksum = "6cf780eb737f2d4a49ffbd512324d53ad089070f813f7be7f99dbd5123a7f448" [[package]] name = "addr2line" -version = "0.24.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" @@ -67,6 +67,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" +[[package]] +name = "annotate-snippets" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e35ed54e5ea7997c14ed4c70ba043478db1112e98263b3b035907aa197d991" +dependencies = [ + "anstyle", + "unicode-width", +] + [[package]] name = "anstream" version = "0.6.15" @@ -181,23 +191,23 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", ] [[package]] @@ -258,7 +268,7 @@ dependencies = [ "semver", "serde", "serde_json", - "toml_edit 0.22.21", + "toml_edit 0.22.22", ] [[package]] @@ -394,6 +404,7 @@ dependencies = [ "anyhow", "bevy", "clippy_utils", + "ui_test", ] [[package]] @@ -420,7 +431,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "toml_edit 0.22.21", + "toml_edit 0.22.22", ] [[package]] @@ -583,7 +594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.8", "serde", ] @@ -611,6 +622,15 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + [[package]] name = "cargo-generate" version = "0.22.0" @@ -653,11 +673,34 @@ dependencies = [ "walkdir", ] +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" -version = "1.1.21" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "jobserver", "libc", @@ -678,9 +721,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -688,9 +731,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -701,9 +744,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -738,12 +781,55 @@ dependencies = [ "rustc_apfloat", ] +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "comma" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1021,6 +1107,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "faster-hex" version = "0.9.0" @@ -1186,9 +1282,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" @@ -1216,7 +1312,7 @@ dependencies = [ "gix-utils", "itoa", "thiserror", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -1237,7 +1333,7 @@ dependencies = [ "smallvec", "thiserror", "unicode-bom", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -1340,7 +1436,7 @@ dependencies = [ "itoa", "smallvec", "thiserror", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -1374,7 +1470,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -1448,8 +1544,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1539,9 +1635,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "humantime" @@ -1604,9 +1700,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -1617,7 +1713,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -1642,12 +1737,18 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.8", "same-file", "walkdir", "winapi-util", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.5.0" @@ -1776,11 +1877,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libgit2-sys" @@ -1943,11 +2050,11 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", ] [[package]] @@ -2048,18 +2155,21 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.4" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "openssl" @@ -2117,6 +2227,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + [[package]] name = "parking" version = "2.2.1" @@ -2178,9 +2303,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -2189,9 +2314,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -2199,9 +2324,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", @@ -2212,9 +2337,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -2231,26 +2356,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -2265,15 +2370,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -2290,6 +2395,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettydiff" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abec3fb083c10660b3854367697da94c674e9e82aa7511014dc958beeb7215e9" +dependencies = [ + "owo-colors", + "pad", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -2352,9 +2467,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -2378,8 +2493,8 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -2393,13 +2508,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -2410,29 +2525,29 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "remove_dir_all" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c914caef075f03e9d5c568e2e71b3d3cf17dc61a5481ff379bb744721be0a75a" +checksum = "a694f9e0eb3104451127f6cc1e5de55f59d3b1fc8c5ddfaeb6f1e716479ceb4a" dependencies = [ "cfg-if", "cvt", "fs_at", "libc", "normpath", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", @@ -2531,6 +2646,27 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfix" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f5b7fc8060f4f8373f9381a630304b42e1183535d9beb1d3f596b236c9106a" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "tracing", +] + [[package]] name = "rustix" version = "0.38.37" @@ -2559,19 +2695,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -2639,9 +2774,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -2690,9 +2825,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2792,6 +2927,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spanned" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86af297923fbcfd107c20a189a6e9c872160df71a7190ae4a7a6c5dce4b2feb6" +dependencies = [ + "bstr", + "color-eyre", +] + [[package]] name = "spin" version = "0.9.8" @@ -2824,9 +2969,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -2887,12 +3032,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -2903,18 +3048,18 @@ checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -3057,7 +3202,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.21", + "toml_edit 0.22.22", ] [[package]] @@ -3084,38 +3229,17 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -3154,6 +3278,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -3214,9 +3348,35 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "ui_test" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ee4c40e5a5f9fa6864ff976473e5d6a6e9884b6ce68b40690d9f87e1994c83" +dependencies = [ + "annotate-snippets", + "anyhow", + "bstr", + "cargo-platform", + "cargo_metadata", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "indicatif", + "levenshtein", + "prettydiff", + "regex", + "rustc_version", + "rustfix", + "serde", + "serde_json", + "spanned", +] [[package]] name = "unicode-bidi" @@ -3253,9 +3413,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "untrusted" @@ -3639,9 +3799,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/bevy_lint/Cargo.toml b/bevy_lint/Cargo.toml index bcdb77aa..6ee9537a 100644 --- a/bevy_lint/Cargo.toml +++ b/bevy_lint/Cargo.toml @@ -14,6 +14,10 @@ path = "src/bin/main.rs" name = "bevy_lint_driver" path = "src/bin/driver.rs" +[[test]] +name = "ui" +harness = false + [dependencies] # Easy error propagation and contexts anyhow = "1.0.86" @@ -30,6 +34,9 @@ rev = "e8ac4ea4187498052849531b86114a1eec5314a1" # Used to test lints that need Bevy's types bevy = { version = "0.14.2", default-features = false } +# Ensures the error messages for lints do not regress. +ui_test = "0.26.5" + [package.metadata.rust-analyzer] # Enables Rust-Analyzer support for `rustc` crates. rustc_private = true From fa16ab000f28ac5721927651af6d0279304d0aa5 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:57:30 -0400 Subject: [PATCH 02/35] feat: begin setting up ui test structure --- Cargo.lock | 22 ++++++++++++++----- Cargo.toml | 2 +- bevy_lint/Cargo.toml | 3 --- bevy_lint/tests/ui.rs | 20 +++++++++++++++++ bevy_lint/tests/ui/Cargo.toml | 7 ++++++ .../ui/src/bin/main_return_without_appexit.rs | 10 +++++++++ 6 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 bevy_lint/tests/ui.rs create mode 100644 bevy_lint/tests/ui/Cargo.toml create mode 100644 bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs diff --git a/Cargo.lock b/Cargo.lock index e5f93fa2..0a0fd4fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,7 +402,6 @@ name = "bevy_lint" version = "0.1.0-dev" dependencies = [ "anyhow", - "bevy", "clippy_utils", "ui_test", ] @@ -533,7 +532,7 @@ dependencies = [ "ahash", "bevy_utils_proc_macros", "getrandom", - "hashbrown", + "hashbrown 0.14.5", "thread_local", "tracing", "web-time", @@ -1578,6 +1577,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -1751,12 +1756,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", "serde", ] @@ -3352,6 +3357,13 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "ui" +version = "0.0.0" +dependencies = [ + "bevy", +] + [[package]] name = "ui_test" version = "0.26.5" diff --git a/Cargo.toml b/Cargo.toml index 58047a2c..c48962ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bevy_lint"] +members = ["bevy_lint", "bevy_lint/tests/ui"] [package] name = "bevy_cli" diff --git a/bevy_lint/Cargo.toml b/bevy_lint/Cargo.toml index 6ee9537a..89ae998c 100644 --- a/bevy_lint/Cargo.toml +++ b/bevy_lint/Cargo.toml @@ -31,9 +31,6 @@ git = "https://github.com/rust-lang/rust-clippy" rev = "e8ac4ea4187498052849531b86114a1eec5314a1" [dev-dependencies] -# Used to test lints that need Bevy's types -bevy = { version = "0.14.2", default-features = false } - # Ensures the error messages for lints do not regress. ui_test = "0.26.5" diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs new file mode 100644 index 00000000..f9aef23d --- /dev/null +++ b/bevy_lint/tests/ui.rs @@ -0,0 +1,20 @@ +use ui_test::{run_tests, CommandBuilder, Config}; + +fn main() -> ui_test::color_eyre::Result<()> { + let config = config(); + run_tests(config) +} + +fn config() -> Config { + Config { + host: None, + root_dir: "tests/ui".into(), + program: program(), + out_dir: "../target/ui".into(), + ..todo!() + } +} + +fn program() -> CommandBuilder { + todo!() +} diff --git a/bevy_lint/tests/ui/Cargo.toml b/bevy_lint/tests/ui/Cargo.toml new file mode 100644 index 00000000..5ce3f750 --- /dev/null +++ b/bevy_lint/tests/ui/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ui" +edition = "2021" +publish = false + +[dependencies] +bevy = { version = "0.14.2", default-features = false } diff --git a/bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs b/bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs new file mode 100644 index 00000000..bbfb6a8b --- /dev/null +++ b/bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs @@ -0,0 +1,10 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +#![warn(bevy::pedantic)] + +use bevy::prelude::*; + +fn main() { + App::new().run(); + //~^ WARN: an entrypoint that calls `App::run()` does not return `AppExit` +} From bb29bf882a2d43752fb291980156ffa5799bac34 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:30:43 -0400 Subject: [PATCH 03/35] feat: further progress on entrypoint --- bevy_lint/tests/ui.rs | 52 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index f9aef23d..c8d6c102 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,20 +1,60 @@ -use ui_test::{run_tests, CommandBuilder, Config}; +use std::{ffi::OsString, path::Path}; -fn main() -> ui_test::color_eyre::Result<()> { +use ui_test::{ + color_eyre, default_any_file_filter, run_tests_generic, status_emitter, Args, CommandBuilder, + Config, +}; + +fn main() -> color_eyre::Result<()> { let config = config(); - run_tests(config) + run_lint_tests(config) } fn config() -> Config { Config { - host: None, + host: Some(String::new()), root_dir: "tests/ui".into(), - program: program(), + // program: program(), out_dir: "../target/ui".into(), - ..todo!() + ..Config::dummy() } } fn program() -> CommandBuilder { todo!() } + +fn run_lint_tests(mut config: Config) -> color_eyre::Result<()> { + // Parse arguments from the CLI. + let args = Args::test()?; + + config.with_args(&args); + + run_tests_generic( + vec![config], + // Do not match any files. + |path, config| { + // We can only run Rust files within the `src/bin` folder. + if path.extension()? == "rs" && path.parent()? == Path::new("tests/ui/src/bin") { + return Some(default_any_file_filter(path, config)); + } + + None + }, + // Append `--bin file_stem` to the `cargo check` command. + |config, spanned| { + let mut argument = OsString::from("--bin "); + + let file_stem = spanned + .span + .file + .file_stem() // Like `file_name()`, but it ignores the extension. + .expect("Attempted to test a file without a name."); + + argument.push(file_stem); + + config.program.args.push(argument); + }, + status_emitter::Text::verbose(), + ) +} From 41e057695ee15ce2295c452c523c9fb41b72892f Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:37:40 -0400 Subject: [PATCH 04/35] feat: switch to calling `bevy_lint_driver` directly --- Cargo.lock | 8 +- Cargo.toml | 2 +- bevy_lint/Cargo.toml | 3 + bevy_lint/tests/ui.rs | 75 +++++++------------ bevy_lint/tests/ui/Cargo.toml | 7 -- .../bin => }/main_return_without_appexit.rs | 2 +- .../ui/main_return_without_appexit.stderr | 17 +++++ 7 files changed, 48 insertions(+), 66 deletions(-) delete mode 100644 bevy_lint/tests/ui/Cargo.toml rename bevy_lint/tests/ui/{src/bin => }/main_return_without_appexit.rs (81%) create mode 100644 bevy_lint/tests/ui/main_return_without_appexit.stderr diff --git a/Cargo.lock b/Cargo.lock index 0a0fd4fd..e471ad60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,7 @@ name = "bevy_lint" version = "0.1.0-dev" dependencies = [ "anyhow", + "bevy", "clippy_utils", "ui_test", ] @@ -3357,13 +3358,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "ui" -version = "0.0.0" -dependencies = [ - "bevy", -] - [[package]] name = "ui_test" version = "0.26.5" diff --git a/Cargo.toml b/Cargo.toml index c48962ae..58047a2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bevy_lint", "bevy_lint/tests/ui"] +members = ["bevy_lint"] [package] name = "bevy_cli" diff --git a/bevy_lint/Cargo.toml b/bevy_lint/Cargo.toml index 89ae998c..bb986ccf 100644 --- a/bevy_lint/Cargo.toml +++ b/bevy_lint/Cargo.toml @@ -31,6 +31,9 @@ git = "https://github.com/rust-lang/rust-clippy" rev = "e8ac4ea4187498052849531b86114a1eec5314a1" [dev-dependencies] +# Used when running UI tests. +bevy = { version = "0.14.2", default-features = false } + # Ensures the error messages for lints do not regress. ui_test = "0.26.5" diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index c8d6c102..4eab4f95 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,60 +1,35 @@ -use std::{ffi::OsString, path::Path}; - -use ui_test::{ - color_eyre, default_any_file_filter, run_tests_generic, status_emitter, Args, CommandBuilder, - Config, -}; +use std::{ffi::OsString, path::PathBuf}; +use ui_test::{color_eyre, run_tests, CommandBuilder, Config}; fn main() -> color_eyre::Result<()> { let config = config(); - run_lint_tests(config) + run_tests(config) } fn config() -> Config { Config { host: Some(String::new()), - root_dir: "tests/ui".into(), - // program: program(), - out_dir: "../target/ui".into(), - ..Config::dummy() - } -} - -fn program() -> CommandBuilder { - todo!() -} - -fn run_lint_tests(mut config: Config) -> color_eyre::Result<()> { - // Parse arguments from the CLI. - let args = Args::test()?; - - config.with_args(&args); - - run_tests_generic( - vec![config], - // Do not match any files. - |path, config| { - // We can only run Rust files within the `src/bin` folder. - if path.extension()? == "rs" && path.parent()? == Path::new("tests/ui/src/bin") { - return Some(default_any_file_filter(path, config)); - } - - None + program: { + let mut p = CommandBuilder::rustc(); + + p.program = PathBuf::from("rustup"); + + p.args = [ + "run", + "nightly-2024-08-21", + "../target/debug/bevy_lint_driver", + "--error-format=json", + "-L", + "all=../target/debug/deps", + "--extern=bevy", + ] + .into_iter() + .map(OsString::from) + .collect(); + + p }, - // Append `--bin file_stem` to the `cargo check` command. - |config, spanned| { - let mut argument = OsString::from("--bin "); - - let file_stem = spanned - .span - .file - .file_stem() // Like `file_name()`, but it ignores the extension. - .expect("Attempted to test a file without a name."); - - argument.push(file_stem); - - config.program.args.push(argument); - }, - status_emitter::Text::verbose(), - ) + out_dir: PathBuf::from("../target/ui"), + ..Config::rustc("tests/ui") + } } diff --git a/bevy_lint/tests/ui/Cargo.toml b/bevy_lint/tests/ui/Cargo.toml deleted file mode 100644 index 5ce3f750..00000000 --- a/bevy_lint/tests/ui/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "ui" -edition = "2021" -publish = false - -[dependencies] -bevy = { version = "0.14.2", default-features = false } diff --git a/bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs b/bevy_lint/tests/ui/main_return_without_appexit.rs similarity index 81% rename from bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs rename to bevy_lint/tests/ui/main_return_without_appexit.rs index bbfb6a8b..a08155dc 100644 --- a/bevy_lint/tests/ui/src/bin/main_return_without_appexit.rs +++ b/bevy_lint/tests/ui/main_return_without_appexit.rs @@ -1,6 +1,6 @@ #![feature(register_tool)] #![register_tool(bevy)] -#![warn(bevy::pedantic)] +#![warn(bevy::main_return_without_appexit)] use bevy::prelude::*; diff --git a/bevy_lint/tests/ui/main_return_without_appexit.stderr b/bevy_lint/tests/ui/main_return_without_appexit.stderr new file mode 100644 index 00000000..b6ddb590 --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit.stderr @@ -0,0 +1,17 @@ +warning: an entrypoint that calls `App::run()` does not return `AppExit` + --> tests/ui/main_return_without_appexit.rs:8:16 + | +7 | fn main() { + | - help: try: `-> AppExit` +8 | App::new().run(); + | ^^^^^ + | + = note: `App::run()` returns `AppExit`, which can be used to determine whether the app exited successfully or not +note: the lint level is defined here + --> tests/ui/main_return_without_appexit.rs:3:9 + | +3 | #![warn(bevy::main_return_without_appexit)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: 1 warning emitted + From 9b39f557952230f7b3343f8764044b8bb9d4aad6 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:45:41 -0400 Subject: [PATCH 05/35] chore: add a few comments --- bevy_lint/tests/ui.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 4eab4f95..7e4cb573 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -8,10 +8,13 @@ fn main() -> color_eyre::Result<()> { fn config() -> Config { Config { + // Make this an empty string, because `bevy_lint_driver` does not currently support the + // `--version` flag, which is required to auto-discover the host. host: Some(String::new()), program: { let mut p = CommandBuilder::rustc(); + // Switch from raw `rustc` calls to `rustup run TOOLCHAIN bevy_lint_driver`. p.program = PathBuf::from("rustup"); p.args = [ @@ -19,6 +22,7 @@ fn config() -> Config { "nightly-2024-08-21", "../target/debug/bevy_lint_driver", "--error-format=json", + // This allows examples to `use bevy;`. "-L", "all=../target/debug/deps", "--extern=bevy", From 96829a1c672a8a71d5a72e43fdbd5054662f1964 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 3 Oct 2024 22:35:23 -0400 Subject: [PATCH 06/35] feat: improve lint driver --- bevy_lint/src/bin/driver.rs | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/bevy_lint/src/bin/driver.rs b/bevy_lint/src/bin/driver.rs index d0b34d36..f2ab9c99 100644 --- a/bevy_lint/src/bin/driver.rs +++ b/bevy_lint/src/bin/driver.rs @@ -2,16 +2,51 @@ #![feature(rustc_private)] extern crate rustc_driver; +extern crate rustc_session; extern crate rustc_span; +use std::process::ExitCode; + use bevy_lint::BevyLintCallback; -use rustc_span::ErrorGuaranteed; +use rustc_driver::{catch_with_exit_code, init_rustc_env_logger, install_ice_hook, RunCompiler}; +use rustc_session::{config::ErrorOutputType, EarlyDiagCtxt}; + +const BUG_REPORT_URL: &str = "https://github.com/TheBevyFlock/bevy_cli/issues"; + +fn main() -> ExitCode { + // Setup a diagnostic context that can be used for error messages. + let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); + + // Setup `rustc`'s builtin `tracing` logger. + init_rustc_env_logger(&early_dcx); + + // "ICE" stands for Internal Compiler Error. An ICE hook is a special type of panic handler + // that dumps an absurd amount of data when the driver panics. We take advantage of this, but + // override the default bug report URL so that users bother us, not Rust compiler devs. :) + install_ice_hook(BUG_REPORT_URL, |dcx| { + dcx.handle() + .note("This is likely a bug with `bevy_lint`, not `rustc` or `cargo`."); + }); + + // Run the passed closure, but catch any panics and return the respective exit code. + let exit_code = catch_with_exit_code(move || { + // Get the arguments passed through the CLI. This is equivalent to `std::env::args()`, but + // it returns a `Result` instead of panicking. + let mut args = rustc_driver::args::raw_args(&early_dcx)?; + + // The arguments are formatted as `[DRIVER_PATH, RUSTC_PATH, ARGS...]`. We skip the driver + // path so that `RunCompiler` just sees `rustc`'s path. + args.remove(0); + + println!("{:?}", args); -fn main() -> Result<(), ErrorGuaranteed> { - // The arguments are formatted as `[DRIVER_PATH, RUSTC_PATH, ARGS...]`. We skip the driver path - // so that `RunCompiler` just sees `rustc`'s path. - let args: Vec = std::env::args().skip(1).collect(); + // Call the compiler with our custom callback. + RunCompiler::new(&args, &mut BevyLintCallback).run() + }); - // Call the compiler with our custom callback. - rustc_driver::RunCompiler::new(&args, &mut BevyLintCallback).run() + // We truncate the `i32` to a `u8`. `catch_with_exit_code()` currently only returns 1 or 0, so + // this should does not discard any data. We prefer returning an `ExitCode` instead of calling + // `std::process::exit()` because this calls `Drop` implementations, in case we need them in + // the future. + ExitCode::from(exit_code as u8) } From 4147d4553e1e9a02b3db73235793648b4a518803 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 4 Oct 2024 08:37:12 -0400 Subject: [PATCH 07/35] fix!: remove `println!()` statement --- bevy_lint/src/bin/driver.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bevy_lint/src/bin/driver.rs b/bevy_lint/src/bin/driver.rs index f2ab9c99..f989f653 100644 --- a/bevy_lint/src/bin/driver.rs +++ b/bevy_lint/src/bin/driver.rs @@ -38,8 +38,6 @@ fn main() -> ExitCode { // path so that `RunCompiler` just sees `rustc`'s path. args.remove(0); - println!("{:?}", args); - // Call the compiler with our custom callback. RunCompiler::new(&args, &mut BevyLintCallback).run() }); From 0f735b2c4d147a65a8b0cf579ad3431310a1c921 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:01:49 -0400 Subject: [PATCH 08/35] feat: IT WORKS MWAHAHAHAH --- bevy_lint/tests/ui.rs | 1 + bevy_lint/tests/ui/main_return_without_appexit.rs | 4 ++-- bevy_lint/tests/ui/main_return_without_appexit.stderr | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 7e4cb573..7cd9c4af 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -21,6 +21,7 @@ fn config() -> Config { "run", "nightly-2024-08-21", "../target/debug/bevy_lint_driver", + "rustc", "--error-format=json", // This allows examples to `use bevy;`. "-L", diff --git a/bevy_lint/tests/ui/main_return_without_appexit.rs b/bevy_lint/tests/ui/main_return_without_appexit.rs index a08155dc..8e6cffb1 100644 --- a/bevy_lint/tests/ui/main_return_without_appexit.rs +++ b/bevy_lint/tests/ui/main_return_without_appexit.rs @@ -1,10 +1,10 @@ #![feature(register_tool)] #![register_tool(bevy)] -#![warn(bevy::main_return_without_appexit)] +#![deny(bevy::main_return_without_appexit)] use bevy::prelude::*; fn main() { App::new().run(); - //~^ WARN: an entrypoint that calls `App::run()` does not return `AppExit` + //~^ ERROR: an entrypoint that calls `App::run()` does not return `AppExit` } diff --git a/bevy_lint/tests/ui/main_return_without_appexit.stderr b/bevy_lint/tests/ui/main_return_without_appexit.stderr index b6ddb590..c5b137f0 100644 --- a/bevy_lint/tests/ui/main_return_without_appexit.stderr +++ b/bevy_lint/tests/ui/main_return_without_appexit.stderr @@ -1,4 +1,4 @@ -warning: an entrypoint that calls `App::run()` does not return `AppExit` +error: an entrypoint that calls `App::run()` does not return `AppExit` --> tests/ui/main_return_without_appexit.rs:8:16 | 7 | fn main() { @@ -10,8 +10,8 @@ warning: an entrypoint that calls `App::run()` does not return `AppExit` note: the lint level is defined here --> tests/ui/main_return_without_appexit.rs:3:9 | -3 | #![warn(bevy::main_return_without_appexit)] +3 | #![deny(bevy::main_return_without_appexit)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: 1 warning emitted +error: aborting due to 1 previous error From de166d5f3b466b358cc7637463c0d823f344117e Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:07:32 -0400 Subject: [PATCH 09/35] refactor: shrink program declaration --- bevy_lint/tests/ui.rs | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 7cd9c4af..25fe2d96 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -11,28 +11,23 @@ fn config() -> Config { // Make this an empty string, because `bevy_lint_driver` does not currently support the // `--version` flag, which is required to auto-discover the host. host: Some(String::new()), - program: { - let mut p = CommandBuilder::rustc(); - - // Switch from raw `rustc` calls to `rustup run TOOLCHAIN bevy_lint_driver`. - p.program = PathBuf::from("rustup"); - - p.args = [ - "run", - "nightly-2024-08-21", - "../target/debug/bevy_lint_driver", - "rustc", - "--error-format=json", + program: CommandBuilder { + program: "rustup".into(), + args: vec![ + "run".into(), + "nightly-2024-08-21".into(), + "../target/debug/bevy_lint_driver".into(), + "rustc".into(), + "--error-format=json".into(), // This allows examples to `use bevy;`. - "-L", - "all=../target/debug/deps", - "--extern=bevy", - ] - .into_iter() - .map(OsString::from) - .collect(); - - p + "-L".into(), + "all=../target/debug/deps".into(), + "--extern=bevy".into(), + ], + out_dir_flag: Some("--out-dir".into()), + input_file_flag: None, + envs: Vec::new(), + cfg_flag: Some("--print=cfg".into()), }, out_dir: PathBuf::from("../target/ui"), ..Config::rustc("tests/ui") From 13e3b49646b6300dd01a7a164635a899acda1289 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:18:28 -0400 Subject: [PATCH 10/35] feat: add comments and ensure `bevy_lint_driver` is built --- bevy_lint/tests/ui.rs | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 25fe2d96..a6cb09ef 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,29 +1,44 @@ -use std::{ffi::OsString, path::PathBuf}; -use ui_test::{color_eyre, run_tests, CommandBuilder, Config}; +use std::path::{Path, PathBuf}; +use ui_test::{color_eyre::{self, eyre::ensure}, run_tests, CommandBuilder, Config}; fn main() -> color_eyre::Result<()> { - let config = config(); + let config = config()?; run_tests(config) } -fn config() -> Config { - Config { - // Make this an empty string, because `bevy_lint_driver` does not currently support the - // `--version` flag, which is required to auto-discover the host. +/// Generates a custom [`Config`] for `bevy_lint`'s UI tests. +fn config() -> color_eyre::Result { + const DRIVER_PATH: &str = "../target/debug/bevy_lint_driver"; + + ensure!(Path::new(DRIVER_PATH).is_file()); + + let config = Config { + // When `host` is `None`, `ui_test` will attempt to auto-discover the host by calling + // `program -vV`. Unfortunately, `bevy_lint_driver` does not yet support the version flag, + // so we manually specify the host as an empty string. This means that, for now, host- + // specific configuration in UI tests will not work. host: Some(String::new()), program: CommandBuilder { + // We call `rustup run` to setup the proper environmental variables, so that + // `bevy_lint_driver` can link to `librustc_driver.so`. program: "rustup".into(), args: vec![ "run".into(), + // TODO: Use `build.rs` to change this dynamically. "nightly-2024-08-21".into(), - "../target/debug/bevy_lint_driver".into(), + DRIVER_PATH.into(), + // `bevy_lint_driver` expects the first argument to be the path to `rustc`. "rustc".into(), + // This is required so that `ui_test` can parse warnings and errors. "--error-format=json".into(), - // This allows examples to `use bevy;`. + // These two lines tell `rustc` to search in `target/debug/deps` for dependencies. + // This is required for UI tests to import `bevy`. "-L".into(), "all=../target/debug/deps".into(), + // This lets UI tests write `use bevy::*;` without `extern bevy;` first. "--extern=bevy".into(), ], + out_dir_flag: Some("--out-dir".into()), input_file_flag: None, envs: Vec::new(), @@ -31,5 +46,7 @@ fn config() -> Config { }, out_dir: PathBuf::from("../target/ui"), ..Config::rustc("tests/ui") - } + }; + + Ok(config) } From f58bd11382ec19f740a641695a1ebc5bf945bcb3 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:19:53 -0400 Subject: [PATCH 11/35] feat: use rust toolchain specified in `build.rs` --- bevy_lint/tests/ui.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index a6cb09ef..bde867f0 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,6 +1,9 @@ use std::path::{Path, PathBuf}; use ui_test::{color_eyre::{self, eyre::ensure}, run_tests, CommandBuilder, Config}; +// This is set by `build.rs`. It is the version specified in `rust-toolchain.toml`. +const RUST_TOOLCHAIN_CHANNEL: &str = env!("RUST_TOOLCHAIN_CHANNEL"); + fn main() -> color_eyre::Result<()> { let config = config()?; run_tests(config) @@ -24,8 +27,7 @@ fn config() -> color_eyre::Result { program: "rustup".into(), args: vec![ "run".into(), - // TODO: Use `build.rs` to change this dynamically. - "nightly-2024-08-21".into(), + RUST_TOOLCHAIN_CHANNEL.into(), DRIVER_PATH.into(), // `bevy_lint_driver` expects the first argument to be the path to `rustc`. "rustc".into(), From 0fb3828d25439ef88f8c02372aeea1a4167ca5fb Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:23:40 -0400 Subject: [PATCH 12/35] refactor: rustfmt --- bevy_lint/tests/ui.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index bde867f0..7bfcadfe 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,5 +1,8 @@ use std::path::{Path, PathBuf}; -use ui_test::{color_eyre::{self, eyre::ensure}, run_tests, CommandBuilder, Config}; +use ui_test::{ + color_eyre::{self, eyre::ensure}, + run_tests, CommandBuilder, Config, +}; // This is set by `build.rs`. It is the version specified in `rust-toolchain.toml`. const RUST_TOOLCHAIN_CHANNEL: &str = env!("RUST_TOOLCHAIN_CHANNEL"); From 7355e1db5d88b9ba8366f68eba90fcce74e8e6f1 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:32:20 -0400 Subject: [PATCH 13/35] chore: bump `ui_test` to 0.27.1 --- Cargo.lock | 47 +++++++++++++++++++++----------------------- bevy_lint/Cargo.toml | 2 +- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 126ab2b1..966aa93d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -968,9 +968,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.24" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -1541,9 +1541,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1551,15 +1551,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1576,21 +1576,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -2668,12 +2668,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" @@ -3192,9 +3189,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "rustls-pki-types", @@ -3256,9 +3253,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -3887,9 +3884,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ui_test" -version = "0.26.5" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ee4c40e5a5f9fa6864ff976473e5d6a6e9884b6ce68b40690d9f87e1994c83" +checksum = "180a1250feba0214b892e22c3a14e9f6688cc084d7b45ec4672106fcb7914641" dependencies = [ "annotate-snippets", "anyhow", diff --git a/bevy_lint/Cargo.toml b/bevy_lint/Cargo.toml index 0dfba9cf..b52f503a 100644 --- a/bevy_lint/Cargo.toml +++ b/bevy_lint/Cargo.toml @@ -47,7 +47,7 @@ toml_edit = { version = "0.22.22", default-features = false, features = [ bevy = { version = "0.14.2", default-features = false } # Ensures the error messages for lints do not regress. -ui_test = "0.26.5" +ui_test = "0.27.1" [package.metadata.rust-analyzer] # Enables Rust-Analyzer support for `rustc` crates. From d6058f0ab6731ea64e4fa89547d5aedeeb702a92 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:21:05 -0400 Subject: [PATCH 14/35] feat: detect path to `libbevy.rlib` --- Cargo.lock | 2 ++ bevy_lint/Cargo.toml | 4 +++ bevy_lint/tests/ui.rs | 72 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 966aa93d..4ef0abf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -634,6 +634,8 @@ dependencies = [ "anyhow", "bevy", "clippy_utils", + "serde", + "serde_json", "toml_edit 0.22.22", "ui_test", ] diff --git a/bevy_lint/Cargo.toml b/bevy_lint/Cargo.toml index b52f503a..28046faa 100644 --- a/bevy_lint/Cargo.toml +++ b/bevy_lint/Cargo.toml @@ -46,6 +46,10 @@ toml_edit = { version = "0.22.22", default-features = false, features = [ # Used when running UI tests. bevy = { version = "0.14.2", default-features = false } +# Used to deserialize `--message-format=json` messages from Cargo. +serde = { version = "1.0.210", features = ["derive"] } +serde_json = "1.0.128" + # Ensures the error messages for lints do not regress. ui_test = "0.27.1" diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 7bfcadfe..6ca91d5e 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,4 +1,12 @@ -use std::path::{Path, PathBuf}; +// A convenience feature used in `find_bevy_rlib()`. +#![feature(let_chains)] + +use serde::Deserialize; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; use ui_test::{ color_eyre::{self, eyre::ensure}, run_tests, CommandBuilder, Config, @@ -18,6 +26,14 @@ fn config() -> color_eyre::Result { ensure!(Path::new(DRIVER_PATH).is_file()); + let bevy_extern_argument = match find_bevy_rlib() { + Ok(path) => format!("--extern=bevy={}", path.display()).into(), + Err(error) => { + eprintln!("Error while finding path to `libbevy.rlib`: {:?}", error); + "--extern=bevy".into() + } + }; + let config = Config { // When `host` is `None`, `ui_test` will attempt to auto-discover the host by calling // `program -vV`. Unfortunately, `bevy_lint_driver` does not yet support the version flag, @@ -41,7 +57,7 @@ fn config() -> color_eyre::Result { "-L".into(), "all=../target/debug/deps".into(), // This lets UI tests write `use bevy::*;` without `extern bevy;` first. - "--extern=bevy".into(), + bevy_extern_argument, ], out_dir_flag: Some("--out-dir".into()), @@ -55,3 +71,55 @@ fn config() -> color_eyre::Result { Ok(config) } + +#[derive(Deserialize, Debug)] +#[serde(rename = "compiler-artifact", tag = "reason")] +struct ArtifactMessage<'a> { + package_id: &'a str, + target: ArtifactTarget<'a>, + filenames: Vec<&'a Path>, +} + +#[derive(Deserialize, Debug)] +struct ArtifactTarget<'a> { + name: &'a str, + kind: Vec<&'a str>, +} + +fn find_bevy_rlib() -> color_eyre::Result { + let output = Command::new("cargo") + .arg("build") + .arg("--test=ui") + .arg("--message-format=json") + .stderr(Stdio::inherit()) + .output()?; + + ensure!(output.status.success()); + + const NEWLINE: u8 = '\n' as u8; + const BEVY_PACKAGE_ID_PREFIX: &str = + "registry+https://github.com/rust-lang/crates.io-index#bevy@"; + + let mut messages = Vec::with_capacity(1); + + for line in output.stdout.split(|&byte| byte == NEWLINE) { + if let Ok(message) = serde_json::from_slice::(line) + && message.package_id.starts_with(BEVY_PACKAGE_ID_PREFIX) + && message.target.name == "bevy" + && message.target.kind == ["lib"] + { + messages.push(message); + } + } + + ensure!(messages.len() == 1); + + let rlib = messages[0] + .filenames + .iter() + .filter(|p| p.extension() == Some(OsStr::new("rlib"))) + .next() + .unwrap(); + + Ok(rlib.to_path_buf()) +} From f2702e88764535fe3b2bc4a08bd2528d1ca70b43 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:25:38 -0400 Subject: [PATCH 15/35] refactor: use `find()` instead of `filter().next()` --- bevy_lint/tests/ui.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 6ca91d5e..c89e9f79 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -117,8 +117,7 @@ fn find_bevy_rlib() -> color_eyre::Result { let rlib = messages[0] .filenames .iter() - .filter(|p| p.extension() == Some(OsStr::new("rlib"))) - .next() + .find(|p| p.extension() == Some(OsStr::new("rlib"))) .unwrap(); Ok(rlib.to_path_buf()) From 5a2707d2c326f5689a53a26f24b0ccb32d1534fa Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:28:51 -0400 Subject: [PATCH 16/35] refactor: general code improvements --- bevy_lint/tests/ui.rs | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index c89e9f79..8c8bbc04 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -3,6 +3,7 @@ use serde::Deserialize; use std::{ + env, ffi::OsStr, path::{Path, PathBuf}, process::{Command, Stdio}, @@ -22,17 +23,17 @@ fn main() -> color_eyre::Result<()> { /// Generates a custom [`Config`] for `bevy_lint`'s UI tests. fn config() -> color_eyre::Result { - const DRIVER_PATH: &str = "../target/debug/bevy_lint_driver"; + const DRIVER_STEM: &str = "../target/debug/bevy_lint_driver"; - ensure!(Path::new(DRIVER_PATH).is_file()); + // The path to the `bevy_lint_driver` executable, relative from inside the `bevy_lint` folder. + // We use `with_extension()` to potentially add the `.exe` suffix, if on Windows. + let driver_path = Path::new(DRIVER_STEM).with_extension(env::consts::EXE_EXTENSION); - let bevy_extern_argument = match find_bevy_rlib() { - Ok(path) => format!("--extern=bevy={}", path.display()).into(), - Err(error) => { - eprintln!("Error while finding path to `libbevy.rlib`: {:?}", error); - "--extern=bevy".into() - } - }; + ensure!( + driver_path.is_file(), + "`bevy_lint_driver` could not be found at {}, make sure to build it with `cargo build -p bevy_lint --bin bevy_lint_driver`.", + driver_path.display(), + ); let config = Config { // When `host` is `None`, `ui_test` will attempt to auto-discover the host by calling @@ -47,7 +48,7 @@ fn config() -> color_eyre::Result { args: vec![ "run".into(), RUST_TOOLCHAIN_CHANNEL.into(), - DRIVER_PATH.into(), + driver_path.into(), // `bevy_lint_driver` expects the first argument to be the path to `rustc`. "rustc".into(), // This is required so that `ui_test` can parse warnings and errors. @@ -56,10 +57,9 @@ fn config() -> color_eyre::Result { // This is required for UI tests to import `bevy`. "-L".into(), "all=../target/debug/deps".into(), - // This lets UI tests write `use bevy::*;` without `extern bevy;` first. - bevy_extern_argument, + // Make the `bevy` crate directly importable from the UI tests. + format!("--extern=bevy={}", find_bevy_rlib()?.display()).into(), ], - out_dir_flag: Some("--out-dir".into()), input_file_flag: None, envs: Vec::new(), @@ -72,6 +72,12 @@ fn config() -> color_eyre::Result { Ok(config) } +/// An artifact message printed to stdout by Cargo. +/// +/// This only deserializes the fields necessary to run UI tests, the rest of skipped. +/// +/// See for more +/// information on the exact format. #[derive(Deserialize, Debug)] #[serde(rename = "compiler-artifact", tag = "reason")] struct ArtifactMessage<'a> { @@ -80,17 +86,28 @@ struct ArtifactMessage<'a> { filenames: Vec<&'a Path>, } +/// The `"target"` field of an [`ArtifactMessage`]. #[derive(Deserialize, Debug)] struct ArtifactTarget<'a> { name: &'a str, kind: Vec<&'a str>, } +/// Tries to find the path to `libbevy.rlib` that UI tests import. +/// +/// `bevy` is a dev-dependency, and as such is only built for tests and examples. We can force it +/// to be built by calling `cargo build --test=ui --message-format=json`, then scan the printed +/// JSON for the artifact message with the path to `libbevy.rlib`. +/// +/// The reason we specify `--extern bevy=PATH` instead of just `--extern bevy` is because `rustc` +/// will fail to compile if multiple `libbevy.rlib` files are found, which usually is the case. fn find_bevy_rlib() -> color_eyre::Result { + // `bevy` is a dev-dependency, so building a test will require it to be built as well. let output = Command::new("cargo") .arg("build") .arg("--test=ui") .arg("--message-format=json") + // Show error messages to the user for easier debugging. .stderr(Stdio::inherit()) .output()?; From ccdb3a8fd302af051dd2fe4c1c350bcb490253d2 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:52:38 -0400 Subject: [PATCH 17/35] chore: improve comments and error messages --- bevy_lint/tests/ui.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/bevy_lint/tests/ui.rs b/bevy_lint/tests/ui.rs index 8c8bbc04..2213ef43 100644 --- a/bevy_lint/tests/ui.rs +++ b/bevy_lint/tests/ui.rs @@ -1,4 +1,5 @@ -// A convenience feature used in `find_bevy_rlib()`. +// A convenience feature used in `find_bevy_rlib()` that lets you chain multiple `if let` +// statements together with `&&`. #![feature(let_chains)] use serde::Deserialize; @@ -111,31 +112,40 @@ fn find_bevy_rlib() -> color_eyre::Result { .stderr(Stdio::inherit()) .output()?; - ensure!(output.status.success()); + ensure!(output.status.success(), "`cargo build --test=ui` failed."); - const NEWLINE: u8 = '\n' as u8; + // The package ID of the `bevy` crate starts with this string. const BEVY_PACKAGE_ID_PREFIX: &str = "registry+https://github.com/rust-lang/crates.io-index#bevy@"; + // It's theoretically possible for there to be multiple messages about building `libbevy.rlib`. + // We support this, but optimize for just 1 message. let mut messages = Vec::with_capacity(1); - for line in output.stdout.split(|&byte| byte == NEWLINE) { + // Iterate over each line in stdout, trying to deserialize it from JSON. + for line in output.stdout.split(|&byte| byte == b'\n') { if let Ok(message) = serde_json::from_slice::(line) + // If the message passes the following conditions, it's probably the one we want. && message.package_id.starts_with(BEVY_PACKAGE_ID_PREFIX) && message.target.name == "bevy" - && message.target.kind == ["lib"] + && message.target.kind.contains(&"lib") { messages.push(message); } } - ensure!(messages.len() == 1); + ensure!( + messages.len() == 1, + "More than one `libbevy.rlib` was built for UI tests. Please ensure there is not more than 1 version of Bevy in `Cargo.lock`.", + ); + // The message usually has multiple files, often `libbevy.rlib` and `libbevy.rmeta`. Filter + // through these to find the `rlib`. let rlib = messages[0] .filenames .iter() .find(|p| p.extension() == Some(OsStr::new("rlib"))) - .unwrap(); + .expect("`libbevy.rlib` not found within artifact message filenames."); Ok(rlib.to_path_buf()) } From cad6bc6fc6895581df23dd615818b1bfd3d6d804 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:20:46 -0400 Subject: [PATCH 18/35] feat: add ui tests for `main_return_without_appexit` --- .../ui/main_return_without_appexit/base.rs | 13 +++++++++++++ .../ui/main_return_without_appexit/base.stderr | 18 ++++++++++++++++++ .../ui/main_return_without_appexit/bug_87.rs | 17 +++++++++++++++++ .../main_return_without_appexit/bug_87.stderr | 18 ++++++++++++++++++ .../ui/main_return_without_appexit/bug_94.rs | 18 ++++++++++++++++++ .../return_appexit.rs | 14 ++++++++++++++ .../return_result.rs | 16 ++++++++++++++++ .../return_unit.rs} | 3 ++- .../return_unit.stderr} | 11 ++++++----- 9 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/base.rs create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/base.stderr create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/bug_87.rs create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/bug_87.stderr create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/bug_94.rs create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/return_appexit.rs create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/return_result.rs rename bevy_lint/tests/ui/{main_return_without_appexit.rs => main_return_without_appexit/return_unit.rs} (85%) rename bevy_lint/tests/ui/{main_return_without_appexit.stderr => main_return_without_appexit/return_unit.stderr} (64%) diff --git a/bevy_lint/tests/ui/main_return_without_appexit/base.rs b/bevy_lint/tests/ui/main_return_without_appexit/base.rs new file mode 100644 index 00000000..5c51dc16 --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/base.rs @@ -0,0 +1,13 @@ +//! Tests the most basic version: where `main()` returns nothing and `AppExit` is not handled. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +fn main() { + //~^ HELP: try + App::new().run(); + //~^ ERROR: an entrypoint that calls `App::run()` does not return `AppExit` +} diff --git a/bevy_lint/tests/ui/main_return_without_appexit/base.stderr b/bevy_lint/tests/ui/main_return_without_appexit/base.stderr new file mode 100644 index 00000000..25b8d66a --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/base.stderr @@ -0,0 +1,18 @@ +error: an entrypoint that calls `App::run()` does not return `AppExit` + --> tests/ui/main_return_without_appexit/base.rs:11:16 + | +9 | fn main() { + | - help: try: `-> AppExit` +10 | +11 | App::new().run(); + | ^^^^^ + | + = note: `App::run()` returns `AppExit`, which can be used to determine whether the app exited successfully or not +note: the lint level is defined here + --> tests/ui/main_return_without_appexit/base.rs:5:9 + | +5 | #![deny(bevy::main_return_without_appexit)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/main_return_without_appexit/bug_87.rs b/bevy_lint/tests/ui/main_return_without_appexit/bug_87.rs new file mode 100644 index 00000000..ad24da6c --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/bug_87.rs @@ -0,0 +1,17 @@ +//! This test tracks the bug reported in [#87]. When this starts failing, the bug has been fixed. +//! +//! [#87]: https://github.com/TheBevyFlock/bevy_cli/issues/87 + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +fn main() { + // This should not raise an error, since `AppExit` is not ignored. + let app_exit = App::new().run(); + //~^ ERROR: an entrypoint that calls `App::run()` does not return `AppExit` + + println!("{app_exit:?}"); +} diff --git a/bevy_lint/tests/ui/main_return_without_appexit/bug_87.stderr b/bevy_lint/tests/ui/main_return_without_appexit/bug_87.stderr new file mode 100644 index 00000000..ae808d8a --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/bug_87.stderr @@ -0,0 +1,18 @@ +error: an entrypoint that calls `App::run()` does not return `AppExit` + --> tests/ui/main_return_without_appexit/bug_87.rs:13:31 + | +11 | fn main() { + | - help: try: `-> AppExit` +12 | // This should not raise an error, since `AppExit` is not ignored. +13 | let app_exit = App::new().run(); + | ^^^^^ + | + = note: `App::run()` returns `AppExit`, which can be used to determine whether the app exited successfully or not +note: the lint level is defined here + --> tests/ui/main_return_without_appexit/bug_87.rs:7:9 + | +7 | #![deny(bevy::main_return_without_appexit)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/main_return_without_appexit/bug_94.rs b/bevy_lint/tests/ui/main_return_without_appexit/bug_94.rs new file mode 100644 index 00000000..eec61215 --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/bug_94.rs @@ -0,0 +1,18 @@ +//! This test tracks the bug reported in [#94]. When this starts failing, the bug has been fixed. +//! +//! [#94]: https://github.com/TheBevyFlock/bevy_cli/issues/94 + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +fn main() { + let mut app = App::new(); + + // This should error because the `AppExit` is not handled, but it does not. + App::run(&mut app); +} diff --git a/bevy_lint/tests/ui/main_return_without_appexit/return_appexit.rs b/bevy_lint/tests/ui/main_return_without_appexit/return_appexit.rs new file mode 100644 index 00000000..f289d25b --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/return_appexit.rs @@ -0,0 +1,14 @@ +//! Tests when `main()` returns `AppExit`, meaning the user has fixed the lint. No diagnostics +//! should be emitted in this case. + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +fn main() -> AppExit { + App::new().run() +} diff --git a/bevy_lint/tests/ui/main_return_without_appexit/return_result.rs b/bevy_lint/tests/ui/main_return_without_appexit/return_result.rs new file mode 100644 index 00000000..cafb120f --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/return_result.rs @@ -0,0 +1,16 @@ +//! Tests when `main()` returns a type other than the unit `()`. When this is done no lint is +//! emitted, since we assume the user knows what they're doing. + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +fn main() -> Result<(), ()> { + App::new().run(); + + Ok(()) +} diff --git a/bevy_lint/tests/ui/main_return_without_appexit.rs b/bevy_lint/tests/ui/main_return_without_appexit/return_unit.rs similarity index 85% rename from bevy_lint/tests/ui/main_return_without_appexit.rs rename to bevy_lint/tests/ui/main_return_without_appexit/return_unit.rs index 8e6cffb1..aea3087d 100644 --- a/bevy_lint/tests/ui/main_return_without_appexit.rs +++ b/bevy_lint/tests/ui/main_return_without_appexit/return_unit.rs @@ -4,7 +4,8 @@ use bevy::prelude::*; -fn main() { +fn main() -> () { + //~^ HELP: try App::new().run(); //~^ ERROR: an entrypoint that calls `App::run()` does not return `AppExit` } diff --git a/bevy_lint/tests/ui/main_return_without_appexit.stderr b/bevy_lint/tests/ui/main_return_without_appexit/return_unit.stderr similarity index 64% rename from bevy_lint/tests/ui/main_return_without_appexit.stderr rename to bevy_lint/tests/ui/main_return_without_appexit/return_unit.stderr index c5b137f0..8b3966d6 100644 --- a/bevy_lint/tests/ui/main_return_without_appexit.stderr +++ b/bevy_lint/tests/ui/main_return_without_appexit/return_unit.stderr @@ -1,14 +1,15 @@ error: an entrypoint that calls `App::run()` does not return `AppExit` - --> tests/ui/main_return_without_appexit.rs:8:16 + --> tests/ui/main_return_without_appexit/return_unit.rs:9:16 | -7 | fn main() { - | - help: try: `-> AppExit` -8 | App::new().run(); +7 | fn main() -> () { + | -- help: try: `AppExit` +8 | +9 | App::new().run(); | ^^^^^ | = note: `App::run()` returns `AppExit`, which can be used to determine whether the app exited successfully or not note: the lint level is defined here - --> tests/ui/main_return_without_appexit.rs:3:9 + --> tests/ui/main_return_without_appexit/return_unit.rs:3:9 | 3 | #![deny(bevy::main_return_without_appexit)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From f763ea85e3dfb521583b48d9df49f81c9e81ed56 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:04:45 -0400 Subject: [PATCH 19/35] feat: add ui tests for `insert_event_resource` --- .../tests/ui/insert_event_resource/bug_94.rs | 22 ++++++++++ .../tests/ui/insert_event_resource/main.fixed | 26 ++++++++++++ .../tests/ui/insert_event_resource/main.rs | 26 ++++++++++++ .../ui/insert_event_resource/main.stderr | 40 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 bevy_lint/tests/ui/insert_event_resource/bug_94.rs create mode 100644 bevy_lint/tests/ui/insert_event_resource/main.fixed create mode 100644 bevy_lint/tests/ui/insert_event_resource/main.rs create mode 100644 bevy_lint/tests/ui/insert_event_resource/main.stderr diff --git a/bevy_lint/tests/ui/insert_event_resource/bug_94.rs b/bevy_lint/tests/ui/insert_event_resource/bug_94.rs new file mode 100644 index 00000000..8e7a5d4e --- /dev/null +++ b/bevy_lint/tests/ui/insert_event_resource/bug_94.rs @@ -0,0 +1,22 @@ +//! This test tracks the bug reported in [#94]. When this starts failing, the bug has been fixed. +//! +//! [#94]: https://github.com/TheBevyFlock/bevy_cli/issues/94 + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::insert_event_resource)] + +use bevy::prelude::*; + +#[derive(Event)] +struct Foo; + +fn main() { + let mut app = App::new(); + + // These both should error, but currently do not. + App::init_resource::>(&mut app); + App::insert_resource::>(&mut app, Default::default()); +} diff --git a/bevy_lint/tests/ui/insert_event_resource/main.fixed b/bevy_lint/tests/ui/insert_event_resource/main.fixed new file mode 100644 index 00000000..2b1265c7 --- /dev/null +++ b/bevy_lint/tests/ui/insert_event_resource/main.fixed @@ -0,0 +1,26 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::insert_event_resource)] + +use bevy::prelude::*; + +#[derive(Event)] +struct Foo; + +fn main() { + App::new().add_event::(); + //~^ ERROR: called `App::init_resource::>()` instead of `App::add_event::()` + + App::new().add_event::(); + //~^ ERROR: called `App::insert_resource(Events)` instead of `App::add_event::()` + + // Make sure the correct type is detected, even when not explicitly passed to + // `insert_resource()`. + let implied_event: Events = Default::default(); + App::new().add_event::(); + //~^ ERROR: called `App::insert_resource(Events)` instead of `App::add_event::()` + + // Ensure the lint can be muted by annotating the expression. + #[allow(bevy::insert_event_resource)] + App::new().init_resource::>(); +} diff --git a/bevy_lint/tests/ui/insert_event_resource/main.rs b/bevy_lint/tests/ui/insert_event_resource/main.rs new file mode 100644 index 00000000..b48959c8 --- /dev/null +++ b/bevy_lint/tests/ui/insert_event_resource/main.rs @@ -0,0 +1,26 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::insert_event_resource)] + +use bevy::prelude::*; + +#[derive(Event)] +struct Foo; + +fn main() { + App::new().init_resource::>(); + //~^ ERROR: called `App::init_resource::>()` instead of `App::add_event::()` + + App::new().insert_resource::>(Default::default()); + //~^ ERROR: called `App::insert_resource(Events)` instead of `App::add_event::()` + + // Make sure the correct type is detected, even when not explicitly passed to + // `insert_resource()`. + let implied_event: Events = Default::default(); + App::new().insert_resource(implied_event); + //~^ ERROR: called `App::insert_resource(Events)` instead of `App::add_event::()` + + // Ensure the lint can be muted by annotating the expression. + #[allow(bevy::insert_event_resource)] + App::new().init_resource::>(); +} diff --git a/bevy_lint/tests/ui/insert_event_resource/main.stderr b/bevy_lint/tests/ui/insert_event_resource/main.stderr new file mode 100644 index 00000000..0211c215 --- /dev/null +++ b/bevy_lint/tests/ui/insert_event_resource/main.stderr @@ -0,0 +1,40 @@ +error: called `App::init_resource::>()` instead of `App::add_event::()` + --> tests/ui/insert_event_resource/main.rs:11:16 + | +11 | App::new().init_resource::>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/insert_event_resource/main.rs:3:9 + | +3 | #![deny(bevy::insert_event_resource)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: inserting an `Events` resource does not fully setup that event + | +11 | App::new().add_event::(); + | ~~~~~~~~~~~~~~~~~~ + +error: called `App::insert_resource(Events)` instead of `App::add_event::()` + --> tests/ui/insert_event_resource/main.rs:14:16 + | +14 | App::new().insert_resource::>(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: inserting an `Events` resource does not fully setup that event + | +14 | App::new().add_event::(); + | ~~~~~~~~~~~~~~~~~~ + +error: called `App::insert_resource(Events)` instead of `App::add_event::()` + --> tests/ui/insert_event_resource/main.rs:20:16 + | +20 | App::new().insert_resource(implied_event); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: inserting an `Events` resource does not fully setup that event + | +20 | App::new().add_event::(); + | ~~~~~~~~~~~~~~~~~~ + +error: aborting due to 3 previous errors + From 53c7d943cfa68070f5c0702a548ab600f4b60c4e Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:46:45 -0400 Subject: [PATCH 20/35] feat: add ui tests for `panicking_methods` --- bevy_lint/tests/ui/panicking_methods/query.rs | 34 ++++++++ .../tests/ui/panicking_methods/query.stderr | 39 +++++++++ .../tests/ui/panicking_methods/query_state.rs | 25 ++++++ .../ui/panicking_methods/query_state.stderr | 23 +++++ bevy_lint/tests/ui/panicking_methods/world.rs | 52 +++++++++++ .../tests/ui/panicking_methods/world.stderr | 87 +++++++++++++++++++ 6 files changed, 260 insertions(+) create mode 100644 bevy_lint/tests/ui/panicking_methods/query.rs create mode 100644 bevy_lint/tests/ui/panicking_methods/query.stderr create mode 100644 bevy_lint/tests/ui/panicking_methods/query_state.rs create mode 100644 bevy_lint/tests/ui/panicking_methods/query_state.stderr create mode 100644 bevy_lint/tests/ui/panicking_methods/world.rs create mode 100644 bevy_lint/tests/ui/panicking_methods/world.stderr diff --git a/bevy_lint/tests/ui/panicking_methods/query.rs b/bevy_lint/tests/ui/panicking_methods/query.rs new file mode 100644 index 00000000..ff4b33a0 --- /dev/null +++ b/bevy_lint/tests/ui/panicking_methods/query.rs @@ -0,0 +1,34 @@ +//! The tests ths `panicking_query_methods` lint, specifically when triggered on the `Query` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::panicking_query_methods)] + +use bevy::prelude::*; + +#[derive(Component)] +struct Foo; + +fn main() { + App::new().add_systems(Startup, my_system); +} + +fn my_system(mut query: Query<&mut Foo>) { + query.single(); + //~^ ERROR: called a `Query` method that can panic when a non-panicking alternative exists + //~| HELP: use `query.get_single()` + + query.single_mut(); + //~^ ERROR: called a `Query` method that can panic when a non-panicking alternative exists + //~| HELP: use `query.get_single_mut()` + + let entities = [Entity::PLACEHOLDER; 3]; + + let [_, _, _] = query.many(entities); + //~^ ERROR: called a `Query` method that can panic when a non-panicking alternative exists + //~| HELP: use `query.get_many(entities)` + + query.many_mut([]); + //~^ ERROR: called a `Query` method that can panic when a non-panicking alternative exists + //~| HELP: use `query.get_many_mut([])` +} diff --git a/bevy_lint/tests/ui/panicking_methods/query.stderr b/bevy_lint/tests/ui/panicking_methods/query.stderr new file mode 100644 index 00000000..72bb45ac --- /dev/null +++ b/bevy_lint/tests/ui/panicking_methods/query.stderr @@ -0,0 +1,39 @@ +error: called a `Query` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/query.rs:17:11 + | +17 | query.single(); + | ^^^^^^^^ + | + = help: use `query.get_single()` and handle the `Option` or `Result` +note: the lint level is defined here + --> tests/ui/panicking_methods/query.rs:5:9 + | +5 | #![deny(bevy::panicking_query_methods)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called a `Query` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/query.rs:21:11 + | +21 | query.single_mut(); + | ^^^^^^^^^^^^ + | + = help: use `query.get_single_mut()` and handle the `Option` or `Result` + +error: called a `Query` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/query.rs:27:27 + | +27 | let [_, _, _] = query.many(entities); + | ^^^^^^^^^^^^^^ + | + = help: use `query.get_many(entities)` and handle the `Option` or `Result` + +error: called a `Query` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/query.rs:31:11 + | +31 | query.many_mut([]); + | ^^^^^^^^^^^^ + | + = help: use `query.get_many_mut([])` and handle the `Option` or `Result` + +error: aborting due to 4 previous errors + diff --git a/bevy_lint/tests/ui/panicking_methods/query_state.rs b/bevy_lint/tests/ui/panicking_methods/query_state.rs new file mode 100644 index 00000000..19946625 --- /dev/null +++ b/bevy_lint/tests/ui/panicking_methods/query_state.rs @@ -0,0 +1,25 @@ +//! The tests ths `panicking_query_methods` lint, specifically when triggered on the `QueryState` +//! type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::panicking_query_methods)] + +use bevy::prelude::*; + +#[derive(Component)] +struct Foo; + +fn main() { + let mut world = World::new(); + + let mut query_state = QueryState::<&mut Foo>::new(&mut world); + + let _ = query_state.single(&world); + //~^ ERROR: called a `QueryState` method that can panic when a non-panicking alternative exists + //~| HELP: use `query_state.get_single(&world)` + + query_state.single_mut(&mut world); + //~^ ERROR: called a `QueryState` method that can panic when a non-panicking alternative exists + //~| HELP: use `query_state.get_single_mut(&mut world)` +} diff --git a/bevy_lint/tests/ui/panicking_methods/query_state.stderr b/bevy_lint/tests/ui/panicking_methods/query_state.stderr new file mode 100644 index 00000000..0096e772 --- /dev/null +++ b/bevy_lint/tests/ui/panicking_methods/query_state.stderr @@ -0,0 +1,23 @@ +error: called a `QueryState` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/query_state.rs:18:25 + | +18 | let _ = query_state.single(&world); + | ^^^^^^^^^^^^^^ + | + = help: use `query_state.get_single(&world)` and handle the `Option` or `Result` +note: the lint level is defined here + --> tests/ui/panicking_methods/query_state.rs:6:9 + | +6 | #![deny(bevy::panicking_query_methods)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called a `QueryState` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/query_state.rs:22:17 + | +22 | query_state.single_mut(&mut world); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `query_state.get_single_mut(&mut world)` and handle the `Option` or `Result` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/panicking_methods/world.rs b/bevy_lint/tests/ui/panicking_methods/world.rs new file mode 100644 index 00000000..cba18ea2 --- /dev/null +++ b/bevy_lint/tests/ui/panicking_methods/world.rs @@ -0,0 +1,52 @@ +//! The tests ths `panicking_query_methods` lint, specifically when triggered on the `World` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::panicking_world_methods)] + +use bevy::prelude::*; + +#[derive(Component)] +struct Bob; + +#[derive(Resource)] +struct Jeffrey; + +// A non-send resource. +struct Patrick; + +fn main() { + let mut world = World::new(); + + let bob = world.spawn(Bob).id(); + + world.entity(bob); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.entity_mut(bob); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.many_entities([bob]); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.many_entities_mut([bob]); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.resource::(); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.resource_mut::(); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.resource_ref::(); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.non_send_resource::(); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.non_send_resource_mut::(); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + + world.schedule_scope(Update, |_world, _schedule| {}); + //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists +} diff --git a/bevy_lint/tests/ui/panicking_methods/world.stderr b/bevy_lint/tests/ui/panicking_methods/world.stderr new file mode 100644 index 00000000..d4aa6cc9 --- /dev/null +++ b/bevy_lint/tests/ui/panicking_methods/world.stderr @@ -0,0 +1,87 @@ +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:23:11 + | +23 | world.entity(bob); + | ^^^^^^^^^^^ + | + = help: use `world.get_entity(bob)` and handle the `Option` or `Result` +note: the lint level is defined here + --> tests/ui/panicking_methods/world.rs:5:9 + | +5 | #![deny(bevy::panicking_world_methods)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:26:11 + | +26 | world.entity_mut(bob); + | ^^^^^^^^^^^^^^^ + | + = help: use `world.get_entity_mut(bob)` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:29:11 + | +29 | world.many_entities([bob]); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_many_entities([bob])` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:32:11 + | +32 | world.many_entities_mut([bob]); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_many_entities_mut([bob])` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:35:11 + | +35 | world.resource::(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_resource::()` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:38:11 + | +38 | world.resource_mut::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_resource_mut::()` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:41:11 + | +41 | world.resource_ref::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_resource_ref::()` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:44:11 + | +44 | world.non_send_resource::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_non_send_resource::()` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:47:11 + | +47 | world.non_send_resource_mut::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.get_non_send_resource_mut::()` and handle the `Option` or `Result` + +error: called a `World` method that can panic when a non-panicking alternative exists + --> tests/ui/panicking_methods/world.rs:50:11 + | +50 | world.schedule_scope(Update, |_world, _schedule| {}); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `world.try_schedule_scope(Update, |_world, _schedule| {})` and handle the `Option` or `Result` + +error: aborting due to 10 previous errors + From 71e35886b0710185bd40dd2534759051e3577226 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 8 Oct 2024 22:12:17 -0400 Subject: [PATCH 21/35] feat: add help messages to panicking world methods test --- bevy_lint/tests/ui/panicking_methods/world.rs | 10 ++++++ .../tests/ui/panicking_methods/world.stderr | 36 +++++++++---------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/bevy_lint/tests/ui/panicking_methods/world.rs b/bevy_lint/tests/ui/panicking_methods/world.rs index cba18ea2..1e20ac17 100644 --- a/bevy_lint/tests/ui/panicking_methods/world.rs +++ b/bevy_lint/tests/ui/panicking_methods/world.rs @@ -22,31 +22,41 @@ fn main() { world.entity(bob); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_entity(bob)` world.entity_mut(bob); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_entity_mut(bob)` world.many_entities([bob]); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_many_entities([bob])` world.many_entities_mut([bob]); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_many_entities_mut([bob])` world.resource::(); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_resource::()` world.resource_mut::(); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_resource_mut::()` world.resource_ref::(); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_resource_ref::()` world.non_send_resource::(); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_non_send_resource::()` world.non_send_resource_mut::(); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.get_non_send_resource_mut::()` world.schedule_scope(Update, |_world, _schedule| {}); //~^ ERROR: called a `World` method that can panic when a non-panicking alternative exists + //~| HELP: use `world.try_schedule_scope(Update, |_world, _schedule| {})` } diff --git a/bevy_lint/tests/ui/panicking_methods/world.stderr b/bevy_lint/tests/ui/panicking_methods/world.stderr index d4aa6cc9..dd246ff4 100644 --- a/bevy_lint/tests/ui/panicking_methods/world.stderr +++ b/bevy_lint/tests/ui/panicking_methods/world.stderr @@ -12,73 +12,73 @@ note: the lint level is defined here | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:26:11 + --> tests/ui/panicking_methods/world.rs:27:11 | -26 | world.entity_mut(bob); +27 | world.entity_mut(bob); | ^^^^^^^^^^^^^^^ | = help: use `world.get_entity_mut(bob)` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:29:11 + --> tests/ui/panicking_methods/world.rs:31:11 | -29 | world.many_entities([bob]); +31 | world.many_entities([bob]); | ^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_many_entities([bob])` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:32:11 + --> tests/ui/panicking_methods/world.rs:35:11 | -32 | world.many_entities_mut([bob]); +35 | world.many_entities_mut([bob]); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_many_entities_mut([bob])` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:35:11 + --> tests/ui/panicking_methods/world.rs:39:11 | -35 | world.resource::(); +39 | world.resource::(); | ^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_resource::()` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:38:11 + --> tests/ui/panicking_methods/world.rs:43:11 | -38 | world.resource_mut::(); +43 | world.resource_mut::(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_resource_mut::()` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:41:11 + --> tests/ui/panicking_methods/world.rs:47:11 | -41 | world.resource_ref::(); +47 | world.resource_ref::(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_resource_ref::()` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:44:11 + --> tests/ui/panicking_methods/world.rs:51:11 | -44 | world.non_send_resource::(); +51 | world.non_send_resource::(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_non_send_resource::()` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:47:11 + --> tests/ui/panicking_methods/world.rs:55:11 | -47 | world.non_send_resource_mut::(); +55 | world.non_send_resource_mut::(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.get_non_send_resource_mut::()` and handle the `Option` or `Result` error: called a `World` method that can panic when a non-panicking alternative exists - --> tests/ui/panicking_methods/world.rs:50:11 + --> tests/ui/panicking_methods/world.rs:59:11 | -50 | world.schedule_scope(Update, |_world, _schedule| {}); +59 | world.schedule_scope(Update, |_world, _schedule| {}); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `world.try_schedule_scope(Update, |_world, _schedule| {})` and handle the `Option` or `Result` From ee212a8feb66843f0b5e196d63df5a706ada5420 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 8 Oct 2024 22:13:15 -0400 Subject: [PATCH 22/35] refactor: rename `base.rs` to `main.rs` --- .../tests/ui/main_return_without_appexit/{base.rs => main.rs} | 0 .../main_return_without_appexit/{base.stderr => main.stderr} | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename bevy_lint/tests/ui/main_return_without_appexit/{base.rs => main.rs} (100%) rename bevy_lint/tests/ui/main_return_without_appexit/{base.stderr => main.stderr} (81%) diff --git a/bevy_lint/tests/ui/main_return_without_appexit/base.rs b/bevy_lint/tests/ui/main_return_without_appexit/main.rs similarity index 100% rename from bevy_lint/tests/ui/main_return_without_appexit/base.rs rename to bevy_lint/tests/ui/main_return_without_appexit/main.rs diff --git a/bevy_lint/tests/ui/main_return_without_appexit/base.stderr b/bevy_lint/tests/ui/main_return_without_appexit/main.stderr similarity index 81% rename from bevy_lint/tests/ui/main_return_without_appexit/base.stderr rename to bevy_lint/tests/ui/main_return_without_appexit/main.stderr index 25b8d66a..9f036cd9 100644 --- a/bevy_lint/tests/ui/main_return_without_appexit/base.stderr +++ b/bevy_lint/tests/ui/main_return_without_appexit/main.stderr @@ -1,5 +1,5 @@ error: an entrypoint that calls `App::run()` does not return `AppExit` - --> tests/ui/main_return_without_appexit/base.rs:11:16 + --> tests/ui/main_return_without_appexit/main.rs:11:16 | 9 | fn main() { | - help: try: `-> AppExit` @@ -9,7 +9,7 @@ error: an entrypoint that calls `App::run()` does not return `AppExit` | = note: `App::run()` returns `AppExit`, which can be used to determine whether the app exited successfully or not note: the lint level is defined here - --> tests/ui/main_return_without_appexit/base.rs:5:9 + --> tests/ui/main_return_without_appexit/main.rs:5:9 | 5 | #![deny(bevy::main_return_without_appexit)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 985777cda183ae045f019d94f9f43d5db9ae48ce Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 8 Oct 2024 22:25:02 -0400 Subject: [PATCH 23/35] feat: add muted tests for `main_return_without_appexit` --- .../src/lints/main_return_without_appexit.rs | 5 +++++ .../main_return_without_appexit/muted_expr.rs | 19 +++++++++++++++++++ .../muted_expr.stderr | 18 ++++++++++++++++++ .../muted_function.rs | 15 +++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/muted_expr.rs create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/muted_expr.stderr create mode 100644 bevy_lint/tests/ui/main_return_without_appexit/muted_function.rs diff --git a/bevy_lint/src/lints/main_return_without_appexit.rs b/bevy_lint/src/lints/main_return_without_appexit.rs index 555fafc6..5c3334a2 100644 --- a/bevy_lint/src/lints/main_return_without_appexit.rs +++ b/bevy_lint/src/lints/main_return_without_appexit.rs @@ -9,6 +9,11 @@ //! it from `main()` will set the exit code, which allows external processes to detect whether there //! was an error. //! +//! # Known issues +//! +//! If you wish to silence this lint, you must add `#[allow(bevy::main_return_without_appexit)]` to +//! `fn main()`, not the line that calls `App::run()`. +//! //! # Example //! //! ``` diff --git a/bevy_lint/tests/ui/main_return_without_appexit/muted_expr.rs b/bevy_lint/tests/ui/main_return_without_appexit/muted_expr.rs new file mode 100644 index 00000000..33e7b764 --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/muted_expr.rs @@ -0,0 +1,19 @@ +//! A version of `main.rs` where the lint is muted on the expression. Since this lint is run on +//! functions, it will still error. +//! +//! This behavior is misleading, so this test ensures it is not accidentally fixed without updating +//! the documentation. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +fn main() { + //~^ HELP: try + + #[allow(bevy::main_return_without_appexit)] + App::new().run(); + //~^ ERROR: an entrypoint that calls `App::run()` does not return `AppExit` +} diff --git a/bevy_lint/tests/ui/main_return_without_appexit/muted_expr.stderr b/bevy_lint/tests/ui/main_return_without_appexit/muted_expr.stderr new file mode 100644 index 00000000..be356e5b --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/muted_expr.stderr @@ -0,0 +1,18 @@ +error: an entrypoint that calls `App::run()` does not return `AppExit` + --> tests/ui/main_return_without_appexit/muted_expr.rs:17:16 + | +13 | fn main() { + | - help: try: `-> AppExit` +... +17 | App::new().run(); + | ^^^^^ + | + = note: `App::run()` returns `AppExit`, which can be used to determine whether the app exited successfully or not +note: the lint level is defined here + --> tests/ui/main_return_without_appexit/muted_expr.rs:9:9 + | +9 | #![deny(bevy::main_return_without_appexit)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/main_return_without_appexit/muted_function.rs b/bevy_lint/tests/ui/main_return_without_appexit/muted_function.rs new file mode 100644 index 00000000..49bce949 --- /dev/null +++ b/bevy_lint/tests/ui/main_return_without_appexit/muted_function.rs @@ -0,0 +1,15 @@ +//! A version of `main.rs` where the lint is muted on the function. This should pass without any +//! errors. + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::main_return_without_appexit)] + +use bevy::prelude::*; + +#[allow(bevy::main_return_without_appexit)] +fn main() { + App::new().run(); +} From 4086aa321f18a5659eff8d05cf85391fc3387cd9 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Tue, 8 Oct 2024 22:40:10 -0400 Subject: [PATCH 24/35] feat: add ui tests for `plugin_not_ending_in_plugin` --- .../ui/plugin_not_ending_in_plugin/main.rs | 34 +++++++++++++++++++ .../plugin_not_ending_in_plugin/main.stderr | 19 +++++++++++ .../mute_struct.rs | 24 +++++++++++++ .../mute_struct.stderr | 19 +++++++++++ .../spoofed_name.rs | 27 +++++++++++++++ .../spoofed_name.stderr | 19 +++++++++++ 6 files changed, 142 insertions(+) create mode 100644 bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.rs create mode 100644 bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.stderr create mode 100644 bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.rs create mode 100644 bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.stderr create mode 100644 bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.rs create mode 100644 bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.stderr diff --git a/bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.rs b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.rs new file mode 100644 index 00000000..26096e09 --- /dev/null +++ b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.rs @@ -0,0 +1,34 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::plugin_not_ending_in_plugin)] + +use bevy::prelude::*; + +// This should raise an error, since it does not end in "Plugin". +struct Foo; +//~^ HELP: rename the plugin + +//~v ERROR: implemented `Plugin` for a structure whose name does not end in "Plugin" +impl Plugin for Foo { + fn build(&self, _app: &mut App) {} +} + +// This should _not_ raise an error, since it ends in "Plugin". +struct BarPlugin; + +impl Plugin for BarPlugin { + fn build(&self, _app: &mut App) {} +} + +// Though this does not end in "Plugin", the lint is silenced for the `impl` blog, so no error is +// raised. +struct Baz; + +#[allow(bevy::plugin_not_ending_in_plugin)] +impl Plugin for Baz { + fn build(&self, _app: &mut App) {} +} + +fn main() { + App::new().add_plugins((Foo, BarPlugin, Baz)); +} diff --git a/bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.stderr b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.stderr new file mode 100644 index 00000000..f41b6670 --- /dev/null +++ b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/main.stderr @@ -0,0 +1,19 @@ +error: implemented `Plugin` for a structure whose name does not end in "Plugin" + --> tests/ui/plugin_not_ending_in_plugin/main.rs:12:1 + | +8 | struct Foo; + | --- help: rename the plugin: `FooPlugin` +... +12 | / impl Plugin for Foo { +13 | | fn build(&self, _app: &mut App) {} +14 | | } + | |_^ + | +note: the lint level is defined here + --> tests/ui/plugin_not_ending_in_plugin/main.rs:3:9 + | +3 | #![deny(bevy::plugin_not_ending_in_plugin)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.rs b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.rs new file mode 100644 index 00000000..1cb07b67 --- /dev/null +++ b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.rs @@ -0,0 +1,24 @@ +//! A test that verifies annotating the structure definition of a plugin does not silence the lint. +//! +//! While this may eventually be desired behavior, this test ensures the behavior does not change +//! without a proper warning. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::plugin_not_ending_in_plugin)] + +use bevy::prelude::*; + +// This `#[allow(...)]` does nothing. +#[allow(bevy::plugin_not_ending_in_plugin)] +struct Foo; +//~^ HELP: rename the plugin + +//~v ERROR: implemented `Plugin` for a structure whose name does not end in "Plugin" +impl Plugin for Foo { + fn build(&self, _app: &mut App) {} +} + +fn main() { + App::new().add_plugins(Foo); +} diff --git a/bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.stderr b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.stderr new file mode 100644 index 00000000..1b026437 --- /dev/null +++ b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/mute_struct.stderr @@ -0,0 +1,19 @@ +error: implemented `Plugin` for a structure whose name does not end in "Plugin" + --> tests/ui/plugin_not_ending_in_plugin/mute_struct.rs:18:1 + | +14 | struct Foo; + | --- help: rename the plugin: `FooPlugin` +... +18 | / impl Plugin for Foo { +19 | | fn build(&self, _app: &mut App) {} +20 | | } + | |_^ + | +note: the lint level is defined here + --> tests/ui/plugin_not_ending_in_plugin/mute_struct.rs:8:9 + | +8 | #![deny(bevy::plugin_not_ending_in_plugin)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.rs b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.rs new file mode 100644 index 00000000..9a3accc0 --- /dev/null +++ b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.rs @@ -0,0 +1,27 @@ +//! A test that ensures a plugin whose name is "spoofed" with `use T as F` does not sneak past the +//! lint. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::plugin_not_ending_in_plugin)] + +use bevy::prelude::*; + +mod bar { + pub mod baz { + pub struct Foo; + //~^ HELP: rename the plugin + } +} + +// We try to be sneaky, but it doesn't work. +use self::bar::baz::Foo as FooPlugin; + +//~v ERROR: implemented `Plugin` for a structure whose name does not end in "Plugin" +impl Plugin for FooPlugin { + fn build(&self, _app: &mut App) {} +} + +fn main() { + App::new().add_plugins(FooPlugin); +} diff --git a/bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.stderr b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.stderr new file mode 100644 index 00000000..cf4100cf --- /dev/null +++ b/bevy_lint/tests/ui/plugin_not_ending_in_plugin/spoofed_name.stderr @@ -0,0 +1,19 @@ +error: implemented `Plugin` for a structure whose name does not end in "Plugin" + --> tests/ui/plugin_not_ending_in_plugin/spoofed_name.rs:21:1 + | +12 | pub struct Foo; + | --- help: rename the plugin: `FooPlugin` +... +21 | / impl Plugin for FooPlugin { +22 | | fn build(&self, _app: &mut App) {} +23 | | } + | |_^ + | +note: the lint level is defined here + --> tests/ui/plugin_not_ending_in_plugin/spoofed_name.rs:6:9 + | +6 | #![deny(bevy::plugin_not_ending_in_plugin)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 7ca8ad375f514d2f937bf55e8a335792bee58a60 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 10 Oct 2024 07:21:03 -0400 Subject: [PATCH 25/35] Warn on `rustc::internal` lints (#134) --- bevy_lint/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bevy_lint/src/lib.rs b/bevy_lint/src/lib.rs index 42c76e1e..c84683ab 100644 --- a/bevy_lint/src/lib.rs +++ b/bevy_lint/src/lib.rs @@ -2,6 +2,8 @@ #![feature(rustc_private)] // Allows chaining `if let` multiple times using `&&`. #![feature(let_chains)] +// Warn on internal `rustc` lints that check for poor usage of internal compiler APIs. +#![warn(rustc::internal)] // This is a list of every single `rustc` crate used within this library. If you need another, add // it here! From 844c6510877ae1a655745ee53f128a8cff8d02ce Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 9 Oct 2024 22:14:09 -0400 Subject: [PATCH 26/35] feat: setup scaffold --- bevy_lint/src/lints/missing_reflect.rs | 18 ++++++++++++++++++ bevy_lint/src/lints/mod.rs | 3 +++ 2 files changed, 21 insertions(+) create mode 100644 bevy_lint/src/lints/missing_reflect.rs diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs new file mode 100644 index 00000000..b7bbe982 --- /dev/null +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -0,0 +1,18 @@ +//! TODO + +use rustc_lint::LateLintPass; +use rustc_session::declare_lint_pass; + +use crate::declare_bevy_lint; + +declare_bevy_lint! { + pub MISSING_REFLECT, + RESTRICTION, + "defined a component, resource, or event without a `Reflect` implementation", +} + +declare_lint_pass! { + MissingReflect => [MISSING_REFLECT.lint] +} + +impl<'tcx> LateLintPass<'tcx> for MissingReflect {} diff --git a/bevy_lint/src/lints/mod.rs b/bevy_lint/src/lints/mod.rs index dd9f134d..e5a91643 100644 --- a/bevy_lint/src/lints/mod.rs +++ b/bevy_lint/src/lints/mod.rs @@ -3,12 +3,14 @@ use rustc_lint::{Lint, LintStore}; pub mod insert_event_resource; pub mod main_return_without_appexit; +pub mod missing_reflect; pub mod panicking_methods; pub mod plugin_not_ending_in_plugin; pub(crate) static LINTS: &[&BevyLint] = &[ insert_event_resource::INSERT_EVENT_RESOURCE, main_return_without_appexit::MAIN_RETURN_WITHOUT_APPEXIT, + missing_reflect::MISSING_REFLECT, panicking_methods::PANICKING_QUERY_METHODS, panicking_methods::PANICKING_WORLD_METHODS, plugin_not_ending_in_plugin::PLUGIN_NOT_ENDING_IN_PLUGIN, @@ -22,6 +24,7 @@ pub(crate) fn register_lints(store: &mut LintStore) { pub(crate) fn register_passes(store: &mut LintStore) { store.register_late_pass(|_| Box::new(insert_event_resource::InsertEventResource)); store.register_late_pass(|_| Box::new(main_return_without_appexit::MainReturnWithoutAppExit)); + store.register_late_pass(|_| Box::new(missing_reflect::MissingReflect)); store.register_late_pass(|_| Box::new(panicking_methods::PanickingMethods)); store.register_late_pass(|_| Box::new(plugin_not_ending_in_plugin::PluginNotEndingInPlugin)); } From 79f6abaf5bb023322567f94d6d835799694b38d7 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:21:51 -0400 Subject: [PATCH 27/35] chore: switch to `check_item()` --- bevy_lint/src/lints/missing_reflect.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index b7bbe982..e174d2c7 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -1,8 +1,8 @@ //! TODO -use rustc_lint::LateLintPass; +use rustc_hir::Item; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; - use crate::declare_bevy_lint; declare_bevy_lint! { @@ -15,4 +15,8 @@ declare_lint_pass! { MissingReflect => [MISSING_REFLECT.lint] } -impl<'tcx> LateLintPass<'tcx> for MissingReflect {} +impl<'tcx> LateLintPass<'tcx> for MissingReflect { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { + todo!() + } +} From 2b48d38b83430bcc24852e1d31d5b9333189a62b Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:49:18 -0400 Subject: [PATCH 28/35] feat: begin lint logic --- bevy_lint/src/lints/missing_reflect.rs | 49 +++++++++++++++++++++++--- bevy_lint/src/paths.rs | 3 ++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index e174d2c7..92beb01b 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -1,9 +1,11 @@ //! TODO -use rustc_hir::Item; +use crate::declare_bevy_lint; +use clippy_utils::def_path_def_ids; +use rustc_hir::def_id::LocalDefId; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TyCtxt; use rustc_session::declare_lint_pass; -use crate::declare_bevy_lint; declare_bevy_lint! { pub MISSING_REFLECT, @@ -16,7 +18,46 @@ declare_lint_pass! { } impl<'tcx> LateLintPass<'tcx> for MissingReflect { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { - todo!() + fn check_crate(&mut self, cx: &LateContext<'tcx>) { + const CHECKED_TRAITS: [&[&str]; 2] = [&crate::paths::COMPONENT, &crate::paths::RESOURCE]; + + // TODO: Convert from `impl` DID to `struct` DID. + let reflect_impls = find_local_trait_impls(cx.tcx, &crate::paths::REFLECT); + + println!("REFLECT: {reflect_impls:?}"); + + for trait_def_path in CHECKED_TRAITS { + // TODO: Convert from `impl` DID to `struct` DID. + let trait_impls = find_local_trait_impls(cx.tcx, trait_def_path); + + for impl_ in trait_impls { + if !reflect_impls.contains(&impl_) { + println!("Found non reflect {trait_def_path:?}: {impl_:?}"); + } + } + } + } +} + +fn find_local_trait_impls(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> Vec { + // TODO: Filter to just be traits, not macros. + let trait_def_ids: Vec<_> = def_path_def_ids(tcx, trait_def_path).collect(); + + if trait_def_ids.is_empty() { + return Vec::new(); } + + // TODO: Warn / debug only? + if trait_def_ids.len() > 1 { + println!("Multiple `DefIds` found for {trait_def_path:?}: {trait_def_ids:?}"); + } + + let local_trait_impls = tcx.all_local_trait_impls(()); + + trait_def_ids + .into_iter() + .filter_map(|trait_def_id| local_trait_impls.get(&trait_def_id)) + .flatten() + .copied() + .collect() } diff --git a/bevy_lint/src/paths.rs b/bevy_lint/src/paths.rs index ddba7077..1adc5602 100644 --- a/bevy_lint/src/paths.rs +++ b/bevy_lint/src/paths.rs @@ -7,8 +7,11 @@ //! [`match_def_path()`](clippy_utils::match_def_path). pub const APP: [&str; 3] = ["bevy_app", "app", "App"]; +pub const COMPONENT: [&str; 3] = ["bevy_ecs", "component", "Component"]; pub const EVENTS: [&str; 3] = ["bevy_ecs", "event", "Events"]; pub const PLUGIN: [&str; 3] = ["bevy_app", "plugin", "Plugin"]; pub const QUERY: [&str; 4] = ["bevy_ecs", "system", "query", "Query"]; pub const QUERY_STATE: [&str; 4] = ["bevy_ecs", "query", "state", "QueryState"]; +pub const REFLECT: [&str; 3] = ["bevy_reflect", "reflect", "Reflect"]; +pub const RESOURCE: [&str; 4] = ["bevy_ecs", "system", "system_param", "Resource"]; pub const WORLD: [&str; 3] = ["bevy_ecs", "world", "World"]; From e2677be89fc172fad2d114efef2b8289cf77b069 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:09:20 -0400 Subject: [PATCH 29/35] fix: filter out non-trait `DefId`s --- bevy_lint/src/lints/missing_reflect.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index 92beb01b..ca2f0402 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -1,8 +1,11 @@ //! TODO use crate::declare_bevy_lint; -use clippy_utils::def_path_def_ids; -use rustc_hir::def_id::LocalDefId; +use clippy_utils::def_path_res; +use rustc_hir::{ + def::{DefKind, Res}, + def_id::{DefId, LocalDefId}, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::declare_lint_pass; @@ -40,8 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { } fn find_local_trait_impls(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> Vec { - // TODO: Filter to just be traits, not macros. - let trait_def_ids: Vec<_> = def_path_def_ids(tcx, trait_def_path).collect(); + let trait_def_ids = find_trait_did_for_def_path(tcx, trait_def_path); if trait_def_ids.is_empty() { return Vec::new(); @@ -61,3 +63,13 @@ fn find_local_trait_impls(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> Vec, trait_def_path: &[&str]) -> Vec { + def_path_res(tcx, trait_def_path) + .into_iter() + .filter_map(|res| match res { + Res::Def(DefKind::Trait, def_id) => Some(def_id), + _ => None, + }) + .collect() +} From c93837eb92140113b4b9c740add2ebb0ee1a42cd Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:08:10 -0400 Subject: [PATCH 30/35] feat: convert from impl `DefId` to struct definition `DefId` --- bevy_lint/src/lints/missing_reflect.rs | 29 +++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index ca2f0402..ed8fd98e 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -7,7 +7,7 @@ use rustc_hir::{ def_id::{DefId, LocalDefId}, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{AdtDef, TyCtxt}; use rustc_session::declare_lint_pass; declare_bevy_lint! { @@ -24,14 +24,16 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { fn check_crate(&mut self, cx: &LateContext<'tcx>) { const CHECKED_TRAITS: [&[&str]; 2] = [&crate::paths::COMPONENT, &crate::paths::RESOURCE]; - // TODO: Convert from `impl` DID to `struct` DID. - let reflect_impls = find_local_trait_impls(cx.tcx, &crate::paths::REFLECT); + let reflect_impls = impl_dids_to_self_ty( + cx.tcx, + find_local_trait_impls(cx.tcx, &crate::paths::REFLECT), + ); println!("REFLECT: {reflect_impls:?}"); for trait_def_path in CHECKED_TRAITS { - // TODO: Convert from `impl` DID to `struct` DID. - let trait_impls = find_local_trait_impls(cx.tcx, trait_def_path); + let trait_impls = + impl_dids_to_self_ty(cx.tcx, find_local_trait_impls(cx.tcx, trait_def_path)); for impl_ in trait_impls { if !reflect_impls.contains(&impl_) { @@ -73,3 +75,20 @@ fn find_trait_did_for_def_path(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> Vec< }) .collect() } + +fn impl_dids_to_self_ty(tcx: TyCtxt<'_>, dids: Vec) -> Vec { + dids.into_iter() + .filter_map(|did| { + let hir_ty = tcx + .hir_node_by_def_id(did) + .expect_item() + .expect_impl() + .self_ty; + + tcx.type_of(hir_ty.hir_id.owner) + .skip_binder() + .ty_adt_def() + .map(AdtDef::did) + }) + .collect() +} From 8dac6e2c202b631275e8a3272c6522eb4ef91c8b Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 11 Oct 2024 07:24:36 -0400 Subject: [PATCH 31/35] refactor: significantly improve code quality and comments --- bevy_lint/src/lints/missing_reflect.rs | 147 +++++++++++++++++-------- 1 file changed, 100 insertions(+), 47 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index ed8fd98e..a9775600 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -1,14 +1,16 @@ //! TODO use crate::declare_bevy_lint; -use clippy_utils::def_path_res; +use clippy_utils::{def_path_res, diagnostics::span_lint}; use rustc_hir::{ def::{DefKind, Res}, def_id::{DefId, LocalDefId}, + Item, ItemKind, Node, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{AdtDef, TyCtxt}; +use rustc_middle::ty::TyCtxt; use rustc_session::declare_lint_pass; +use rustc_span::Span; declare_bevy_lint! { pub MISSING_REFLECT, @@ -20,75 +22,126 @@ declare_lint_pass! { MissingReflect => [MISSING_REFLECT.lint] } +/// A list of traits that are checked for `Reflect` implementations. +/// +/// If a struct implements one of these traits but not `Reflect`, this lint will raise a warning. +const CHECKED_TRAITS: [&[&str]; 2] = [&crate::paths::COMPONENT, &crate::paths::RESOURCE]; + impl<'tcx> LateLintPass<'tcx> for MissingReflect { + // The lint can be summarized in a few steps: + // + // 1. Find all `impl` items for `Reflect`, `Component`, and `Resource`. + // 2. Find the types that these traits are implemented for. + // 3. If there's a type that implements `Component` or `Resource` but *not* `Reflect`, emit a + // diagnostic. + // + // Because we need a list of all `impl` items, not just one at a time, we implement + // `check_crate()` and not `check_item()`. fn check_crate(&mut self, cx: &LateContext<'tcx>) { - const CHECKED_TRAITS: [&[&str]; 2] = [&crate::paths::COMPONENT, &crate::paths::RESOURCE]; - - let reflect_impls = impl_dids_to_self_ty( - cx.tcx, - find_local_trait_impls(cx.tcx, &crate::paths::REFLECT), - ); - - println!("REFLECT: {reflect_impls:?}"); + // Find all `impl` items for `Reflect` in the current crate, then from that find `T` in + // `impl Reflect for T`. + let reflect_types: Vec<_> = find_local_trait_impls(cx.tcx, &crate::paths::REFLECT) + .filter_map(|did| impl_to_source_type(cx.tcx, did).map(|(did, _)| did)) + .collect(); for trait_def_path in CHECKED_TRAITS { - let trait_impls = - impl_dids_to_self_ty(cx.tcx, find_local_trait_impls(cx.tcx, trait_def_path)); - - for impl_ in trait_impls { - if !reflect_impls.contains(&impl_) { - println!("Found non reflect {trait_def_path:?}: {impl_:?}"); + // This is the same as the above `reflect_types`, but this time we are searching for + // one of the checked traits. (`Component` or `Resource`.) + let checked_types: Vec<_> = find_local_trait_impls(cx.tcx, trait_def_path) + .filter_map(|did| impl_to_source_type(cx.tcx, did)) + .collect(); + + // Check if any of the checked types do not implement `Reflect`. If so, emit the lint! + for (impl_, span) in checked_types { + if !reflect_types.contains(&impl_) { + span_lint(cx, MISSING_REFLECT.lint, span, MISSING_REFLECT.lint.desc); } } } } } -fn find_local_trait_impls(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> Vec { - let trait_def_ids = find_trait_did_for_def_path(tcx, trait_def_path); - - if trait_def_ids.is_empty() { - return Vec::new(); - } - - // TODO: Warn / debug only? - if trait_def_ids.len() > 1 { - println!("Multiple `DefIds` found for {trait_def_path:?}: {trait_def_ids:?}"); - } - +/// Returns a list of [`LocalDefId`]s for `impl` blocks where a specified trait is implemented. +/// +/// Note that sometimes multiple traits can be resolved from the same path. (If there are multiple +/// versions of the same crate, for example.) When this is the case, the results for each trait are +/// concatenated together. +fn find_local_trait_impls<'tcx>( + tcx: TyCtxt<'tcx>, + trait_def_path: &[&str], +) -> impl Iterator + 'tcx { + // Find the `DefId`s for a given trait. + let trait_def_ids = trait_def_ids(tcx, trait_def_path); + + // Find a map of all trait `impl` items within the current crate. The key is the `DefId` of the + // trait, and the value is a `Vec` for all `impl` items. let local_trait_impls = tcx.all_local_trait_impls(()); trait_def_ids - .into_iter() .filter_map(|trait_def_id| local_trait_impls.get(&trait_def_id)) .flatten() .copied() - .collect() } -fn find_trait_did_for_def_path(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> Vec { +/// Finds all [`DefId`]s for a given trait path. +/// +/// This returns an interator because multiple items can have the same name and path, such as +/// traits and macros, and because there may be multiple versions of the same crate. The returned +/// [`DefId`]s are guaranteed to point to traits, however, with all others skipped. +fn trait_def_ids(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> impl Iterator { def_path_res(tcx, trait_def_path) .into_iter() .filter_map(|res| match res { Res::Def(DefKind::Trait, def_id) => Some(def_id), _ => None, }) - .collect() } -fn impl_dids_to_self_ty(tcx: TyCtxt<'_>, dids: Vec) -> Vec { - dids.into_iter() - .filter_map(|did| { - let hir_ty = tcx - .hir_node_by_def_id(did) - .expect_item() - .expect_impl() - .self_ty; - - tcx.type_of(hir_ty.hir_id.owner) - .skip_binder() - .ty_adt_def() - .map(AdtDef::did) - }) - .collect() +/// This function locates the source structure definition for a `impl` item [`LocalDefId`]. +/// +/// Given the following code: +/// +/// ``` +/// struct Foo; // ID: A +/// +/// impl Foo {} // ID: B +/// ``` +/// +/// This function will return a [`DefId`] of `A` when passed the [`LocalDefId`] `B`. It even works +/// when the `impl` block implements a trait, such as `impl Bar for Foo`. +/// +/// This function will return [`None`] if `def_id` does not correspond to an `impl` item, or if the +/// type `T` in `impl T` is not an ADT[^0]. References are automatically peeled, so only the +/// underlying type determines the result. +/// +/// [^0]: An algebraic data type. These are most user-defined types, such as structs, enums, and +/// unions. Notably, primitives are not ADTs. See [`TyKind`](rustc_middle::ty::TyKind) for a +/// complete list. +fn impl_to_source_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<(DefId, Span)> { + let node = tcx.hir_node_by_def_id(def_id); + + // Ensure the node is an `impl` item. + let Node::Item(Item { + kind: ItemKind::Impl(impl_), + .. + }) = node + else { + return None; + }; + + // This is the HIR representation of `T` for `impl T`. Note that the HIR representation does + // not contain actual type information, just the qualified path. + let hir_ty = impl_.self_ty; + + // Convert the `rustc_hir::Ty` to a `rustc_middle::ty::Ty`, which is fully resolved with + // complete type information. Note that we can safely call `skip_binder()` because we are + // purely extrating the type's `DefId`, which does not depend on generic or lifetime data. Also + // note the call to `peel_refs()`, which removes references and returns the underlying type. + let ty_adt = tcx + .type_of(hir_ty.hir_id.owner) + .skip_binder() + .peel_refs() + .ty_adt_def()?; + + Some((ty_adt.did(), hir_ty.span)) } From 9fb7858d25c2b0fea011ae3aee4d1f0c8f4ff783 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 11 Oct 2024 07:37:32 -0400 Subject: [PATCH 32/35] fix: let the lint be silenced for individual structures --- bevy_lint/src/lints/missing_reflect.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index a9775600..9fdba858 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -1,11 +1,11 @@ //! TODO use crate::declare_bevy_lint; -use clippy_utils::{def_path_res, diagnostics::span_lint}; +use clippy_utils::{def_path_res, diagnostics::span_lint_hir}; use rustc_hir::{ def::{DefKind, Res}, def_id::{DefId, LocalDefId}, - Item, ItemKind, Node, + Item, ItemKind, Node, OwnerId, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; @@ -54,7 +54,19 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { // Check if any of the checked types do not implement `Reflect`. If so, emit the lint! for (impl_, span) in checked_types { if !reflect_types.contains(&impl_) { - span_lint(cx, MISSING_REFLECT.lint, span, MISSING_REFLECT.lint.desc); + let owner_id = OwnerId { + // This is guaranteed to be a `LocalDefId` because the trait `impl` that it + // came from is also local. + def_id: impl_.expect_local(), + }; + + span_lint_hir( + cx, + MISSING_REFLECT.lint, + owner_id.into(), + span, + MISSING_REFLECT.lint.desc, + ); } } } From 1b672902499003ffbbb41ac7648e4afd8a30daea Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Fri, 11 Oct 2024 19:52:13 -0400 Subject: [PATCH 33/35] feat: create initial `missing_reflect` ui tests --- bevy_lint/tests/ui/missing_reflect/main.rs | 32 +++++++++++++++++++ .../tests/ui/missing_reflect/main.stderr | 20 ++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 bevy_lint/tests/ui/missing_reflect/main.rs create mode 100644 bevy_lint/tests/ui/missing_reflect/main.stderr diff --git a/bevy_lint/tests/ui/missing_reflect/main.rs b/bevy_lint/tests/ui/missing_reflect/main.rs new file mode 100644 index 00000000..07f10d14 --- /dev/null +++ b/bevy_lint/tests/ui/missing_reflect/main.rs @@ -0,0 +1,32 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +#![allow(dead_code)] +#![deny(bevy::missing_reflect)] + +use bevy::{ecs::component::StorageType, prelude::*}; + +#[derive(Component)] +struct Missing; +//~^ ERROR: defined a component, resource, or event without a `Reflect` implementation + +struct MissingImpl; + +// TODO: This behavior is strange. The lint should be emitted on `struct MissingImpl`. +//~v ERROR: defined a component, resource, or event without a `Reflect` implementation +impl Component for MissingImpl { + const STORAGE_TYPE: StorageType = StorageType::Table; +} + +#[derive(Component, Reflect)] +struct Satisfied; + +#[allow(bevy::missing_reflect)] +#[derive(Component)] +struct Muted; + +#[allow(bevy::missing_reflect)] +struct MutedImpl; + +impl Component for MutedImpl { + const STORAGE_TYPE: StorageType = StorageType::Table; +} diff --git a/bevy_lint/tests/ui/missing_reflect/main.stderr b/bevy_lint/tests/ui/missing_reflect/main.stderr new file mode 100644 index 00000000..25e5f215 --- /dev/null +++ b/bevy_lint/tests/ui/missing_reflect/main.stderr @@ -0,0 +1,20 @@ +error: defined a component, resource, or event without a `Reflect` implementation + --> tests/ui/missing_reflect/main.rs:9:8 + | +9 | struct Missing; + | ^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/missing_reflect/main.rs:4:9 + | +4 | #![deny(bevy::missing_reflect)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: defined a component, resource, or event without a `Reflect` implementation + --> tests/ui/missing_reflect/main.rs:16:20 + | +16 | impl Component for MissingImpl { + | ^^^^^^^^^^^ + +error: aborting due to 2 previous errors + From 0ab38fe9719b8d4519bd8415e4358c1a71e86979 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:22:35 -0400 Subject: [PATCH 34/35] fix: diagnostic span should be on structure definition, not `impl` block --- bevy_lint/src/lints/missing_reflect.rs | 13 +++++++------ bevy_lint/tests/ui/missing_reflect/main.rs | 3 +-- bevy_lint/tests/ui/missing_reflect/main.stderr | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index 9fdba858..746f335a 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -10,7 +10,6 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::declare_lint_pass; -use rustc_span::Span; declare_bevy_lint! { pub MISSING_REFLECT, @@ -41,7 +40,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { // Find all `impl` items for `Reflect` in the current crate, then from that find `T` in // `impl Reflect for T`. let reflect_types: Vec<_> = find_local_trait_impls(cx.tcx, &crate::paths::REFLECT) - .filter_map(|did| impl_to_source_type(cx.tcx, did).map(|(did, _)| did)) + .filter_map(|did| impl_to_source_type(cx.tcx, did)) .collect(); for trait_def_path in CHECKED_TRAITS { @@ -52,8 +51,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { .collect(); // Check if any of the checked types do not implement `Reflect`. If so, emit the lint! - for (impl_, span) in checked_types { + for impl_ in checked_types { if !reflect_types.contains(&impl_) { + let ident = cx.tcx.opt_item_ident(impl_).unwrap(); + let owner_id = OwnerId { // This is guaranteed to be a `LocalDefId` because the trait `impl` that it // came from is also local. @@ -64,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { cx, MISSING_REFLECT.lint, owner_id.into(), - span, + ident.span, MISSING_REFLECT.lint.desc, ); } @@ -129,7 +130,7 @@ fn trait_def_ids(tcx: TyCtxt<'_>, trait_def_path: &[&str]) -> impl Iterator, def_id: LocalDefId) -> Option<(DefId, Span)> { +fn impl_to_source_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option { let node = tcx.hir_node_by_def_id(def_id); // Ensure the node is an `impl` item. @@ -155,5 +156,5 @@ fn impl_to_source_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<(DefId, Sp .peel_refs() .ty_adt_def()?; - Some((ty_adt.did(), hir_ty.span)) + Some(ty_adt.did()) } diff --git a/bevy_lint/tests/ui/missing_reflect/main.rs b/bevy_lint/tests/ui/missing_reflect/main.rs index 07f10d14..8314c5ce 100644 --- a/bevy_lint/tests/ui/missing_reflect/main.rs +++ b/bevy_lint/tests/ui/missing_reflect/main.rs @@ -10,9 +10,8 @@ struct Missing; //~^ ERROR: defined a component, resource, or event without a `Reflect` implementation struct MissingImpl; +//~^ ERROR: defined a component, resource, or event without a `Reflect` implementation -// TODO: This behavior is strange. The lint should be emitted on `struct MissingImpl`. -//~v ERROR: defined a component, resource, or event without a `Reflect` implementation impl Component for MissingImpl { const STORAGE_TYPE: StorageType = StorageType::Table; } diff --git a/bevy_lint/tests/ui/missing_reflect/main.stderr b/bevy_lint/tests/ui/missing_reflect/main.stderr index 25e5f215..b95ed818 100644 --- a/bevy_lint/tests/ui/missing_reflect/main.stderr +++ b/bevy_lint/tests/ui/missing_reflect/main.stderr @@ -11,10 +11,10 @@ note: the lint level is defined here | ^^^^^^^^^^^^^^^^^^^^^ error: defined a component, resource, or event without a `Reflect` implementation - --> tests/ui/missing_reflect/main.rs:16:20 + --> tests/ui/missing_reflect/main.rs:12:8 | -16 | impl Component for MissingImpl { - | ^^^^^^^^^^^ +12 | struct MissingImpl; + | ^^^^^^^^^^^ error: aborting due to 2 previous errors From c34a9dbaac4b71d4b2b43499bc83b9e16e7c4ec4 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Sat, 12 Oct 2024 18:41:47 -0400 Subject: [PATCH 35/35] refactor: make check more data-driven --- bevy_lint/src/lints/missing_reflect.rs | 44 +++++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/bevy_lint/src/lints/missing_reflect.rs b/bevy_lint/src/lints/missing_reflect.rs index 746f335a..defdb9fc 100644 --- a/bevy_lint/src/lints/missing_reflect.rs +++ b/bevy_lint/src/lints/missing_reflect.rs @@ -21,11 +21,6 @@ declare_lint_pass! { MissingReflect => [MISSING_REFLECT.lint] } -/// A list of traits that are checked for `Reflect` implementations. -/// -/// If a struct implements one of these traits but not `Reflect`, this lint will raise a warning. -const CHECKED_TRAITS: [&[&str]; 2] = [&crate::paths::COMPONENT, &crate::paths::RESOURCE]; - impl<'tcx> LateLintPass<'tcx> for MissingReflect { // The lint can be summarized in a few steps: // @@ -43,22 +38,22 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { .filter_map(|did| impl_to_source_type(cx.tcx, did)) .collect(); - for trait_def_path in CHECKED_TRAITS { + for checked_trait in CheckedTraits::all() { // This is the same as the above `reflect_types`, but this time we are searching for // one of the checked traits. (`Component` or `Resource`.) - let checked_types: Vec<_> = find_local_trait_impls(cx.tcx, trait_def_path) + let checked_types: Vec<_> = find_local_trait_impls(cx.tcx, checked_trait.def_path()) .filter_map(|did| impl_to_source_type(cx.tcx, did)) .collect(); // Check if any of the checked types do not implement `Reflect`. If so, emit the lint! - for impl_ in checked_types { - if !reflect_types.contains(&impl_) { - let ident = cx.tcx.opt_item_ident(impl_).unwrap(); + for def_id in checked_types { + if !reflect_types.contains(&def_id) { + let ident = cx.tcx.opt_item_ident(def_id).unwrap(); let owner_id = OwnerId { // This is guaranteed to be a `LocalDefId` because the trait `impl` that it // came from is also local. - def_id: impl_.expect_local(), + def_id: def_id.expect_local(), }; span_lint_hir( @@ -66,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { MISSING_REFLECT.lint, owner_id.into(), ident.span, - MISSING_REFLECT.lint.desc, + checked_trait.diagnostic_message(), ); } } @@ -74,6 +69,31 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { } } +enum CheckedTraits { + Component, + Resource, +} + +impl CheckedTraits { + fn all() -> [Self; 2] { + [Self::Component, Self::Resource] + } + + fn def_path(&self) -> &'static [&'static str] { + match self { + Self::Component => &crate::paths::COMPONENT, + Self::Resource => &crate::paths::RESOURCE, + } + } + + fn diagnostic_message(&self) -> &'static str { + match self { + Self::Component => "defined a component without a `Reflect` implementation", + Self::Resource => "defined a resource without a `Reflect` implementation", + } + } +} + /// Returns a list of [`LocalDefId`]s for `impl` blocks where a specified trait is implemented. /// /// Note that sometimes multiple traits can be resolved from the same path. (If there are multiple