diff --git a/.editorconfig b/.editorconfig index fed963eb..a9af370c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,4 +10,9 @@ insert_final_newline = false [*.yml] indent_style = space +indent_size = 2 + +# This makes it a lot less painful to write YAMLs in docs +[*.md] +indent_style = space indent_size = 2 \ No newline at end of file diff --git a/.github/workflows/generate-roblox-std.yml b/.github/workflows/generate-roblox-std.yml index 787b48ed..ad12177e 100644 --- a/.github/workflows/generate-roblox-std.yml +++ b/.github/workflows/generate-roblox-std.yml @@ -11,4 +11,4 @@ jobs: uses: actions/upload-artifact@v1 with: name: roblox - path: roblox.toml \ No newline at end of file + path: roblox.yml \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 00032843..305d4c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## Unreleased ### Added +- Added [new YAML based standard library format](https://kampfkarren.github.io/selene/usage/std.html). The old TOML format is now deprecated and will not have any new functionality added to it, but will be preserved for the forseeable future. + - You can upgrade old TOML standard libraries by running `selene upgrade-std library.toml`, which will create a new .yml file of the same name in the new format. + - This only affects **standard library files**. `selene.toml` has not changed. - Added `debug.resetmemorycategory` to the Roblox standard library. - Added `debug.setmemorycategory` to the Roblox standard library. - Added `--no-summary` option to suppress summary information. +### Changed +- Roblox standard library files are now no longer generated in the project directory, and will be updated automatically every 6 hours. You can update it manually with `selene update-roblox-std`. + - As per the deprecation of TOML standard libraries, you should delete your `roblox.toml` if you have one. + - It is possible to pin a standard library in the same way `roblox.toml` was if you are in an environment where you do not want automatic updates, such as one where you want to limit selene's internet usage. Learn more [on the Roblox Guide documentation page](https://kampfkarren.github.io/selene/roblox.html). + ## [0.17.0] - 2022-04-10 ### Added - Added `start_line`, `start_column`, `end_line`, and `end_column` to JSON diagnostic output. diff --git a/Cargo.lock b/Cargo.lock index 7af2fc51..fd3ece60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.15.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] @@ -32,7 +32,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -43,15 +43,9 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.1" @@ -60,9 +54,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.60" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", @@ -75,18 +69,15 @@ dependencies = [ [[package]] name = "base64" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "base64" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" @@ -95,33 +86,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "bytecount" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0fdd54b507df8f22012890aadd099979befdba27713c767993f8380112ca7c" - -[[package]] -name = "byteorder" -version = "1.4.3" +name = "bumpalo" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] -name = "bytes" -version = "0.4.12" +name = "bytecount" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "either", - "iovec", -] +checksum = "be0fdd54b507df8f22012890aadd099979befdba27713c767993f8380112ca7c" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -145,9 +125,15 @@ dependencies = [ "num-integer", "num-traits", "time", - "winapi 0.3.9", + "winapi", ] +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + [[package]] name = "clap" version = "2.33.3" @@ -163,15 +149,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "codespan" version = "0.9.5" @@ -194,112 +171,47 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -dependencies = [ - "time", - "url 1.7.2", -] - -[[package]] -name = "cookie_store" -version = "0.7.0" +name = "color-eyre" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" +checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90" dependencies = [ - "cookie", - "failure", - "idna 0.1.5", - "log", - "publicsuffix", - "serde", - "serde_json", - "time", - "try_from", - "url 1.7.2", + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", ] [[package]] -name = "core-foundation" -version = "0.9.1" +name = "color-spantrace" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" dependencies = [ - "core-foundation-sys", - "libc", + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", ] [[package]] -name = "core-foundation-sys" -version = "0.8.2" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam-deque" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.0.1", - "cfg-if 0.1.10", - "crossbeam-utils", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" -dependencies = [ - "cfg-if 0.1.10", - "crossbeam-utils", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg 1.0.1", - "cfg-if 0.1.10", - "lazy_static", -] - [[package]] name = "ctor" version = "0.1.20" @@ -329,81 +241,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encoding_rs" -version = "0.8.28" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "cfg-if 1.0.0", + "dirs-sys", ] [[package]] -name = "failure" -version = "0.1.8" +name = "dirs-sys" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ - "backtrace", - "failure_derive", + "libc", + "redox_users", + "winapi", ] [[package]] -name = "failure_derive" -version = "0.1.8" +name = "eyre" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", + "indenter", + "once_cell", ] [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if 1.0.0", "crc32fast", - "libc", "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -411,31 +287,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ "matches", - "percent-encoding 2.1.0", -] - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", + "percent-encoding", ] -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "full_moon" version = "0.15.0" @@ -464,22 +318,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -dependencies = [ - "futures", - "num_cpus", -] - [[package]] name = "getrandom" version = "0.2.3" @@ -493,9 +331,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.24.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "glob" @@ -503,24 +341,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -dependencies = [ - "byteorder", - "bytes", - "fnv", - "futures", - "http", - "indexmap", - "log", - "slab", - "string", - "tokio-io", -] - [[package]] name = "hashbrown" version = "0.9.1" @@ -545,95 +365,12 @@ dependencies = [ "libc", ] -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -dependencies = [ - "bytes", - "futures", - "http", - "tokio-buf", -] - -[[package]] -name = "httparse" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" - -[[package]] -name = "hyper" -version = "0.12.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c843caf6296fc1f93444735205af9ed4e109a539005abb2564ae1d6fad34c52" -dependencies = [ - "bytes", - "futures", - "futures-cpupool", - "h2", - "http", - "http-body", - "httparse", - "iovec", - "itoa", - "log", - "net2", - "rustc_version", - "time", - "tokio", - "tokio-buf", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" -dependencies = [ - "bytes", - "futures", - "hyper", - "native-tls", - "tokio-io", -] - [[package]] name = "id-arena" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.2.3" @@ -651,25 +388,22 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f7280c75fb2e2fc47080ec80ccc481376923acb04501957fc38f935c3de5088" +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ - "autocfg 1.0.1", + "autocfg", "hashbrown", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itoa" version = "0.4.7" @@ -677,13 +411,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "js-sys" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "wasm-bindgen", ] [[package]] @@ -694,39 +427,30 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.97" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] -name = "lock_api" -version = "0.3.4" +name = "linked-hash-map" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "md5" @@ -736,103 +460,17 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" - -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg 1.0.1", -] - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mime_guess" -version = "2.0.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" -dependencies = [ - "mime", - "unicase", -] +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", - "autocfg 1.0.1", -] - -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "native-tls" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", ] [[package]] @@ -841,7 +479,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-traits", ] @@ -851,7 +489,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -866,51 +504,18 @@ dependencies = [ [[package]] name = "object" -version = "0.25.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "openssl" -version = "0.10.34" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" - -[[package]] -name = "openssl-sys" -version = "0.9.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" -dependencies = [ - "autocfg 1.0.1", - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "output_vt100" @@ -918,34 +523,14 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] -name = "parking_lot" -version = "0.9.0" +name = "owo-colors" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api", - "parking_lot_core", - "rustc_version", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "rustc_version", - "smallvec", - "winapi 0.3.9", -] +checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" [[package]] name = "paste" @@ -999,12 +584,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -1012,16 +591,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] -name = "pkg-config" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" - -[[package]] -name = "ppv-lite86" -version = "0.2.10" +name = "pin-project-lite" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pretty_assertions" @@ -1063,180 +636,24 @@ dependencies = [ name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "publicsuffix" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" -dependencies = [ - "idna 0.2.3", - "url 2.2.2", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.9", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.9", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.9", -] +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] -name = "rand_pcg" -version = "0.1.2" +name = "proc-macro2" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", + "unicode-xid", ] [[package]] -name = "rand_xorshift" -version = "0.1.1" +name = "quote" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "rand_core 0.3.1", + "proc-macro2", ] [[package]] @@ -1251,7 +668,7 @@ dependencies = [ "serde", "serde_derive", "serde_repr", - "uuid 0.8.2", + "uuid", ] [[package]] @@ -1267,27 +684,23 @@ dependencies = [ ] [[package]] -name = "rdrand" -version = "0.4.0" +name = "redox_syscall" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ - "rand_core 0.3.1", + "bitflags", ] [[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_syscall" -version = "0.2.9" +name = "redox_users" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "bitflags", + "getrandom", + "redox_syscall", + "thiserror", ] [[package]] @@ -1308,61 +721,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "reqwest" -version = "0.9.24" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88643aea3c1343c804950d7bf983bd2067f5ab59db6d613a08e05572f2714ab" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "base64 0.10.1", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "flate2", - "futures", - "http", - "hyper", - "hyper-tls", - "log", - "mime", - "mime_guess", - "native-tls", - "serde", - "serde_json", - "serde_urlencoded", - "time", - "tokio", - "tokio-executor", - "tokio-io", - "tokio-threadpool", - "tokio-timer", - "url 1.7.2", - "uuid 0.7.4", - "winreg", + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", ] [[package]] name = "rustc-demangle" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] -name = "rustc_version" -version = "0.2.3" +name = "rustls" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ - "semver", + "log", + "ring", + "sct", + "webpki", ] [[package]] @@ -1372,42 +760,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi 0.3.9", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "security-framework" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.3.0" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] @@ -1419,18 +778,21 @@ dependencies = [ "chrono", "codespan", "codespan-reporting", + "color-eyre", + "dirs", "full_moon", "glob", "lazy_static", "num_cpus", - "reqwest", "selene-lib", "serde", "serde_json", + "serde_yaml", "structopt", "termcolor", "threadpool", "toml", + "ureq", ] [[package]] @@ -1449,25 +811,11 @@ dependencies = [ "regex", "serde", "serde_json", + "serde_yaml", "termcolor", "toml", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.126" @@ -1511,30 +859,24 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.5.5" +name = "serde_yaml" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" dependencies = [ - "dtoa", - "itoa", + "indexmap", + "ryu", "serde", - "url 1.7.2", + "yaml-rust", ] [[package]] -name = "slab" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" - -[[package]] -name = "smallvec" -version = "0.6.14" +name = "sharded-slab" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "maybe-uninit", + "lazy_static", ] [[package]] @@ -1547,13 +889,10 @@ dependencies = [ ] [[package]] -name = "string" -version = "0.2.1" +name = "spin" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -dependencies = [ - "bytes", -] +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "strsim" @@ -1596,32 +935,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "synstructure" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand 0.8.4", - "redox_syscall 0.2.9", - "remove_dir_all", - "winapi 0.3.9", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -1641,213 +954,124 @@ dependencies = [ ] [[package]] -name = "threadpool" -version = "1.8.1" +name = "thiserror" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ - "num_cpus", + "thiserror-impl", ] [[package]] -name = "time" -version = "0.1.43" +name = "thiserror-impl" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ - "libc", - "winapi 0.3.9", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "tinyvec" -version = "1.2.0" +name = "thread_local" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "tinyvec_macros", + "once_cell", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "0.1.22" +name = "threadpool" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ - "bytes", - "futures", - "mio", "num_cpus", - "tokio-current-thread", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -dependencies = [ - "bytes", - "either", - "futures", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" -dependencies = [ - "futures", - "tokio-executor", ] [[package]] -name = "tokio-executor" -version = "0.1.10" +name = "time" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ - "crossbeam-utils", - "futures", + "libc", + "winapi", ] [[package]] -name = "tokio-io" -version = "0.1.13" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ - "bytes", - "futures", - "log", + "tinyvec_macros", ] [[package]] -name = "tokio-reactor" -version = "0.1.12" +name = "tinyvec_macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" -dependencies = [ - "crossbeam-utils", - "futures", - "lazy_static", - "log", - "mio", - "num_cpus", - "parking_lot", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] -name = "tokio-sync" -version = "0.1.8" +name = "toml" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "fnv", - "futures", + "serde", ] [[package]] -name = "tokio-tcp" -version = "0.1.4" +name = "tracing" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ - "bytes", - "futures", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-core", ] [[package]] -name = "tokio-threadpool" -version = "0.1.18" +name = "tracing-core" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils", - "futures", "lazy_static", - "log", - "num_cpus", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" -dependencies = [ - "crossbeam-utils", - "futures", - "slab", - "tokio-executor", -] - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", + "valuable", ] [[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "try_from" -version = "0.3.2" +name = "tracing-error" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ - "cfg-if 0.1.10", + "tracing", + "tracing-subscriber", ] [[package]] -name = "unicase" -version = "2.6.0" +name = "tracing-subscriber" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ - "version_check", + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-normalization" @@ -1877,14 +1101,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] -name = "url" -version = "1.7.2" +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", + "base64 0.13.0", + "chunked_transfer", + "flate2", + "log", + "once_cell", + "rustls", + "serde", + "serde_json", + "url", + "webpki", + "webpki-roots", ] [[package]] @@ -1894,18 +1132,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", - "idna 0.2.3", + "idna", "matches", - "percent-encoding 2.1.0", -] - -[[package]] -name = "uuid" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -dependencies = [ - "rand 0.6.5", + "percent-encoding", ] [[package]] @@ -1919,10 +1148,10 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.13" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025ce40a007e1907e58d5bc1a594def78e5573bb0b1160bc389634e8f12e4faa" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vec_map" @@ -1937,27 +1166,93 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] -name = "want" -version = "0.2.0" +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ - "futures", + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", "log", - "try-lock", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", ] [[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +name = "wasm-bindgen-macro" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] [[package]] -name = "winapi" -version = "0.2.8" +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] [[package]] name = "winapi" @@ -1969,12 +1264,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1987,7 +1276,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1997,20 +1286,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "yaml-rust" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "linked-hash-map", ] diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index b45f1d49..9e74c3ed 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -33,3 +33,5 @@ - [undefined_variable](./lints/undefined_variable.md) - [unscoped_variables](./lints/unscoped_variables.md) - [unused_variable](./lints/unused_variable.md) +- [Archive](./archive/index.md) + - [TOML Standard Library Format](./archive/std_v1.md) diff --git a/docs/src/archive/index.md b/docs/src/archive/index.md new file mode 100644 index 00000000..50fc1e51 --- /dev/null +++ b/docs/src/archive/index.md @@ -0,0 +1,2 @@ +# Archive +The following is pages that refer to deprecated or removed features in selene. diff --git a/docs/src/archive/std_v1.md b/docs/src/archive/std_v1.md new file mode 100644 index 00000000..371b5ab3 --- /dev/null +++ b/docs/src/archive/std_v1.md @@ -0,0 +1,192 @@ +# Standard Library Format (v1) +> The TOML standard library format is **DEPRECATED** and will not have any new functionality added onto it. Check out the [updated standard library format here](../usage/std.md). +> +> In order to convert an existing TOML standard library over to the new format, simply run `selene upgrade-std library.toml`, which will create an upgraded `library.yml` file. + +selene provides a robust standard library format to allow for use with environments other than vanilla Lua. Standard libraries are defined in the form of [TOML](https://github.com/toml-lang/toml) files. + +## Examples +For examples of the standard library format, see: +- [`lua51.toml`](https://github.com/Kampfkarren/selene/blob/416be350d7004bf300a83f402712f8df6a3f67da/selene-lib/default_std/lua51.toml) - The default standard library for Lua 5.1 +- [`lua52.toml`](https://github.com/Kampfkarren/selene/blob/416be350d7004bf300a83f402712f8df6a3f67da/selene-lib/default_std/lua52.toml) - A standard library for Lua 5.2's additions and removals. Reference this if your standard library is based off another (it most likely is). +- [`roblox.toml`](https://gist.github.com/evaera/13c96302d308c7a9ffb2a3fc5d28ac96) - A standard library for Roblox that incorporates all the advanced features of the format. If you are a Roblox developer, don't use this as anything other than reference--an up to date version of this library is available with every commit. + +## [selene] +Anything under the key `[selene]` is used for meta information. The following paths are accepted: + +`[selene.base]` - Used for specifying what standard library to be based off of. Currently only accepts built in standard libraries, meaning `lua51` or `lua52`. + +`[selene.name]` - Used for specifying the name of the standard library. Used internally for cases such as only giving Roblox lints if the standard library is named `"roblox"`. + +`[selene.structs]` - Used for declaring [structs](#structs). + +## [globals] +This is where the magic happens. The `globals` field is a dictionary where the keys are the globals you want to define. The value you give tells selene what the value can be, do, and provide. + +If your standard library is based off another, overriding something defined there will use your implementation over the original. + +## Any +Example: +```toml +[foo] +any = true +``` + +Specifies that the field can be used in any possible way, meaning that `foo.x`, `foo:y()`, etc will all validate. + +## Functions +Example: + +```toml +[[tonumber.args]] +type = "any" + +[[tonumber.args]] +type = "number" +required = false +``` + +A field is a function if it contains an `args` and/or `method` field. + +If `method` is specified as `true` and the function is inside a table, then it will require the function be called in the form of `Table:FunctionName()`, instead of `Table.FunctionName()`. + +`args` is an array of arguments, in order of how they're used in the function. An argument is in the form of: + +``` +required?: false | true | string; +type: "any" | "bool" | "function" | "nil" + | "number" | "string" | "table" | "..." + | string[] | { "display": string } +``` + +## "required" +- `true` - The default, this argument is required. +- `false` - This argument is optional. +- A string - This argument is required, and not using it will give this as the reason why. + +## Argument types +- `"any"` - Allows any value. +- `"bool"`, `"function"`, `"nil"`, `"number"`, `"string"`, `"table"` - Expects a value of the respective type. +- `"..."` - Allows any number of variables after this one. If `required` is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle. +- Constant list of strings - Will check if the value provided is one of the strings in the list. For example, `collectgarbage` only takes one of a few exact string arguments--doing `collectgarbage("count")` will work, but `collectgarbage("whoops")` won't. +- `{ "display": string }` - Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method `Color3.toHSV` expects a `Color3` object--no constant inside it could be correct, so this is defined as: + +```toml +[[Color3.toHSV.args]] +type = { display = "Color3" } +``` + +## Properties +Example: + +```toml +[_VERSION] +property = true +``` + +Specifies that a property exists. For example, `_VERSION` is available as a global and doesn't have any fields of its own, so it is just defined as a property. + +The same goes for `_G`, which is defined as: + +```toml +[_G] +property = true +writable = "new-fields" +``` + +`writable` is an optional field that tells selene how the property can be mutated and used: + +- `"new-fields"` - New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that `_G = "foo"` is linted against. +- `"overridden"` - New fields can't be added, but entire variable can be overridden. In the case of Roblox's `Instance.Name`, it means we can do `Instance.Name = "Hello"`, but not `Instance.Name.Call()`. +- `"full"` - New fields can be added and entire variable can be overridden. + +If `writable` is not specified, selene will assume it can neither have new fields associated with it nor can be overridden. + +## Struct +Example: +```toml +[game] +struct = "DataModel" +``` + +Specifies that the field is an instance of a [struct](#structs). The value is the name of the struct. + +## Table +Example: +```toml +[math.huge] +property = true + +[math.pi] +property = true +``` + +A field is understood as a table if it has fields of its own. Notice that `[math]` is not defined anywhere, but its fields are. Fields are of the same type as globals. + +## Removed +Example: +```toml +[getfenv] +removed = true +``` + +Used when your standard library is based off another, and your library removes something from the original. + +## Structs + +Structs are used in places such as Roblox Instances. Every Instance in Roblox, for example, declares a `:GetChildren()` method. We don't want to have to define this everywhere an Instance is declared globally, so instead we just define it once in a struct. + +Structs are defined as fields of `[selene.structs]`. Any fields they have will be used for instances of that struct. For example, the Roblox standard library has the struct: + +```toml +[selene.structs.Event.Connect] +method = true + +[[selene.structs.Event.Connect.args]] +type = "function" +``` + +From there, it can define: + +```toml +[workspace.Changed] +struct = "Event" +``` + +...and selene will know that `workspace.Changed:Connect(callback)` is valid, but `workspace.Changed:RandomNameHere()` is not. + +## Wildcards +Fields can specify requirements if a field is referenced that is not explicitly named. For example, in Roblox, instances can have arbitrary fields of other instances (`workspace.Baseplate` indexes an instance named Baseplate inside `workspace`, but `Baseplate` is nowhere in the Roblox API). + +We can specify this behavior by using the special `"*"` field. + +```toml +[workspace."*"] +struct = "Instance" +``` + +This will tell selene "any field accessed from `workspace` that doesn't exist must be an Instance [struct](#structs)". + +Wildcards can even be used in succession. For example, consider the following: + +```toml +[script.Name] +property = true +writable = "overridden" + +[script."*"."*"] +property = true +writable = "full" +``` + +Ignoring the wildcard, so far this means: + +- `script.Name = "Hello"` *will* work. +- `script = nil` *will not* work, because the writability of `script` is not specified. +- `script.Name.UhOh` *will not* work, because `script.Name` does not have fields. + +However, with the wildcard, this adds extra meaning: + +- `script.Foo = 3` *will not* work, because the writability of `script.*` is not specified. +- `script.Foo.Bar = 3` *will* work, because `script.*.*` has full writability. +- `script.Foo.Bar.Baz = 3` *will* work for the same reason as above. diff --git a/docs/src/roblox.md b/docs/src/roblox.md index b888c4be..81b9d397 100644 --- a/docs/src/roblox.md +++ b/docs/src/roblox.md @@ -8,22 +8,38 @@ If you try to run selene on a Roblox codebase, you'll get a bunch of errors sayi Thankfully, this process is very simple. All you need to do is edit your `selene.toml` (or create one) and add the following: -`std = "roblox"` +```toml +std = "roblox" +``` -The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a `roblox.toml` file and your selene has `std = "roblox"`. +The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a cached standard library file and your `selene.toml` has `std = "roblox"`. -You can also initiate this process manually with the CLI command `selene generate-roblox-std`. +## Updating definitions -Deprecated event members are not added by default. This means code such as `workspace.ChildAdded:connect(...)` will error. If you don't want to lint these, use `selene generate-roblox-std --deprecated`. - -## Updating `roblox.toml` - -If you're wondering why selene is providing you with outdated information regarding API that it doesn't know, you'll need to delete your `roblox.toml` and re-generate it. The Roblox standard library will be automatically updated by running selene after you've deleted `roblox.toml`. +The Roblox standard library file is updated automatically every 6 hours. If you need an update faster than that, you can run `selene update-roblox-std` manually. ## TestEZ Support -Roblox has provided an open source testing utility called [TestEZ](https://roblox.github.io/testez/), which allows you to write unit tests for your code. Writing unit tests is good practice, but selene will get angry at you if you don't include a `testez.toml` file and set the standard library to the following: +Roblox has provided an open source testing utility called [TestEZ](https://roblox.github.io/testez/), which allows you to write unit tests for your code. Writing unit tests is good practice, but selene will get angry at you if you don't include a `testez.yml` file and set the standard library to the following: `std = "roblox+testez"` -But first you'll need to create a `testez.toml` file, which you can do so [with this template.](https://gist.github.com/Nezuo/65af3108a6214a209ca4e329e22af73c) +But first you'll need to create a `testez.yml` file, which you can do so [with this template](https://gist.github.com/Kampfkarren/f2dddc2ebfa4e0662e44b8702e519c2d). + +## Pinned standard library + +There may be cases where you would rather not have selene automatically update the Roblox standard library, such as if speed is critically important and you want to limit potential internet access (generating the standard library requires an active internet connection). + +selene supports "pinning" the standard library to a specific version. + +Add the following to your `selene.toml` configuration: +```toml +# `floating` by default, meaning it is stored in a cache folder on your system +roblox-std-source = "pinned" +``` + +This will generate the standard library file into `roblox.yml` where it is run. + +You can also create a `roblox.yml` file manually with `selene generate-roblox-std`. + +Deprecated event members are not added by default. This means code such as `workspace.ChildAdded:connect(...)` will error. If you don't want to lint these, use `selene generate-roblox-std --deprecated`. diff --git a/docs/src/usage/std.md b/docs/src/usage/std.md index 74f5a8b0..1b225bf0 100644 --- a/docs/src/usage/std.md +++ b/docs/src/usage/std.md @@ -1,45 +1,45 @@ # Standard Library Format -selene provides a robust standard library format to allow for use with environments other than vanilla Lua. Standard libraries are defined in the form of [TOML](https://github.com/toml-lang/toml) files. +selene provides a robust standard library format to allow for use with environments other than vanilla Lua. Standard libraries are defined in the form of [YAML](https://en.wikipedia.org/wiki/YAML) files. ## Examples For examples of the standard library format, see: -- [`lua51.toml`](https://github.com/Kampfkarren/selene/blob/master/selene-lib/default_std/lua51.toml) - The default standard library for Lua 5.1 -- [`lua52.toml`](https://github.com/Kampfkarren/selene/blob/master/selene-lib/default_std/lua52.toml) - A standard library for Lua 5.2's additions and removals. Reference this if your standard library is based off another (it most likely is). -- [`roblox.toml`](https://gist.github.com/evaera/13c96302d308c7a9ffb2a3fc5d28ac96) - A standard library for Roblox that incorporates all the advanced features of the format. If you are a Roblox developer, don't use this as anything other than reference--an up to date version of this library is available with every commit. +- [`lua51.yml`](https://github.com/Kampfkarren/selene/blob/main/selene-lib/default_std/lua51.yml) - The default standard library for Lua 5.1 +- [`lua52.yml`](https://github.com/Kampfkarren/selene/blob/main/selene-lib/default_std/lua52.yml) - A standard library for Lua 5.2's additions and removals. Reference this if your standard library is based off another (it most likely is). +- [`roblox.yml`](https://gist.github.com/Kampfkarren/dff2dc17cc30d68a48510da58fff2381) - A standard library for Roblox that incorporates all the advanced features of the format. If you are a Roblox developer, don't use this as anything other than reference--an up to date version of this library is automatically generated. -## [selene] -Anything under the key `[selene]` is used for meta information. The following paths are accepted: +## base -`[selene.base]` - Used for specifying what standard library to be based off of. Currently only accepts built in standard libraries, meaning `lua51` or `lua52`. +Used for specifying what standard library to be based off of. This supports both builtin libraries (lua51, lua52, lua53, roblox), as well as any standard libraries that can be found in the current directory. -`[selene.name]` - Used for specifying the name of the standard library. Used internally for cases such as only giving Roblox lints if the standard library is named `"roblox"`. - -`[selene.structs]` - Used for declaring [structs](#structs). +```yaml +--- # This begins a YAML file +base: lua51 # We will be extending off of Lua 5.1. +``` -## [globals] +## globals This is where the magic happens. The `globals` field is a dictionary where the keys are the globals you want to define. The value you give tells selene what the value can be, do, and provide. If your standard library is based off another, overriding something defined there will use your implementation over the original. -## Any -Example: -```toml -[foo] -any = true +### Any +```yaml +--- +globals: + foo: + any: true ``` -Specifies that the field can be used in any possible way, meaning that `foo.x`, `foo:y()`, etc will all validate. - -## Functions -Example: - -```toml -[[tonumber.args]] -type = "any" - -[[tonumber.args]] -type = "number" -required = false +This specifies that the field can be used in any possible way, meaning that `foo.x`, `foo:y()`, etc will all validate. + +### Functions +```yaml +--- +globals: + tonumber: + args: + - type: any + - type: number + required: false ``` A field is a function if it contains an `args` and/or `method` field. @@ -55,98 +55,103 @@ type: "any" | "bool" | "function" | "nil" | string[] | { "display": string } ``` -## "required" +#### "required" - `true` - The default, this argument is required. - `false` - This argument is optional. - A string - This argument is required, and not using it will give this as the reason why. -## Argument types +#### Argument types - `"any"` - Allows any value. - `"bool"`, `"function"`, `"nil"`, `"number"`, `"string"`, `"table"` - Expects a value of the respective type. - `"..."` - Allows any number of variables after this one. If `required` is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle. - Constant list of strings - Will check if the value provided is one of the strings in the list. For example, `collectgarbage` only takes one of a few exact string arguments--doing `collectgarbage("count")` will work, but `collectgarbage("whoops")` won't. - `{ "display": string }` - Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method `Color3.toHSV` expects a `Color3` object--no constant inside it could be correct, so this is defined as: -```toml -[[Color3.toHSV.args]] -type = { display = "Color3" } +```yaml +--- +globals: + Color3.toHSV: + args: + - type: + display: Color3 ``` -## Properties -Example: - -```toml -[_VERSION] -property = true +### Properties +```yaml +--- +globals: + _VERSION: + property: read-only ``` Specifies that a property exists. For example, `_VERSION` is available as a global and doesn't have any fields of its own, so it is just defined as a property. The same goes for `_G`, which is defined as: - -```toml -[_G] -property = true -writable = "new-fields" +```yaml +_G: + property: new-fields ``` -`writable` is an optional field that tells selene how the property can be mutated and used: +The value of property tells selene how it can be mutated and used: +- `"read-only"` - New fields cannot be added or set, and the variable itself cannot be redefined. - `"new-fields"` - New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that `_G = "foo"` is linted against. -- `"overridden"` - New fields can't be added, but entire variable can be overridden. In the case of Roblox's `Instance.Name`, it means we can do `Instance.Name = "Hello"`, but not `Instance.Name.Call()`. -- `"full"` - New fields can be added and entire variable can be overridden. - -If `writable` is not specified, selene will assume it can neither have new fields associated with it nor can be overridden. - -## Struct -Example: -```toml -[game] -struct = "DataModel" +- `"override-fields"` - New fields can't be added, but entire variable can be overridden. In the case of Roblox's `Instance.Name`, it means we can do `Instance.Name = "Hello"`, but not `Instance.Name.Call()`. +- `"full-write"` - New fields can be added and entire variable can be overridden. + +### Struct +```yaml +--- +globals: + game: + struct: DataModel ``` Specifies that the field is an instance of a [struct](#structs). The value is the name of the struct. -## Table -Example: -```toml -[math.huge] -property = true - -[math.pi] -property = true +### Tables +```yaml +--- +globals: + math.huge: + property: read-only + math.pi: + property: read-only ``` -A field is understood as a table if it has fields of its own. Notice that `[math]` is not defined anywhere, but its fields are. Fields are of the same type as globals. +A field is understood as a table if it has fields of its own. Notice that `math` is not defined anywhere, but its fields are. This will create an implicit `math` with the property writability of `read-only`. -## Removed -Example: -```toml -[getfenv] -removed = true +### Removed +```yaml +--- +globals: + getfenv: + removed: true ``` -Used when your standard library is based off another, and your library removes something from the original. +Used when your standard library is [based off](#base) another, and your library removes something from the original. ## Structs - Structs are used in places such as Roblox Instances. Every Instance in Roblox, for example, declares a `:GetChildren()` method. We don't want to have to define this everywhere an Instance is declared globally, so instead we just define it once in a struct. -Structs are defined as fields of `[selene.structs]`. Any fields they have will be used for instances of that struct. For example, the Roblox standard library has the struct: +Structs are defined as fields of `structs`. Any fields they have will be used for instances of that struct. For example, the Roblox standard library has the struct: -```toml -[selene.structs.Event.Connect] -method = true - -[[selene.structs.Event.Connect.args]] -type = "function" +```yaml +--- +structs: + Event: + Connect: + method: true + args: + - type: function ``` From there, it can define: -```toml -[workspace.Changed] -struct = "Event" +```yaml +globals: + workspace.Changed: + struct: Event ``` ...and selene will know that `workspace.Changed:Connect(callback)` is valid, but `workspace.Changed:RandomNameHere()` is not. @@ -165,14 +170,12 @@ This will tell selene "any field accessed from `workspace` that doesn't exist mu Wildcards can even be used in succession. For example, consider the following: -```toml -[script.Name] -property = true -writable = "overridden" +```yaml +script.Name: + property: override-fields -[script."*"."*"] -property = true -writable = "full" +script.*.*: + property: full-write ``` Ignoring the wildcard, so far this means: @@ -186,3 +189,15 @@ However, with the wildcard, this adds extra meaning: - `script.Foo = 3` *will not* work, because the writability of `script.*` is not specified. - `script.Foo.Bar = 3` *will* work, because `script.*.*` has full writability. - `script.Foo.Bar.Baz = 3` *will* work for the same reason as above. + +## Internal properties + +There are some properties that exist in standard library YAMLs that exist specifically for internal purposes. This is merely a reference, but these are not guaranteed to be stable. + +### name + +This specifies the name of the standard library. This is used internally for cases such as only giving Roblox lints if the standard library is named `"roblox"`. + +### last_updated + +A timestamp of when the standard library was last updated. This is used by the Roblox standard library generator to update when it gets too old. diff --git a/docs/theme/highlight.js b/docs/theme/highlight.js deleted file mode 100644 index 207a3a3f..00000000 --- a/docs/theme/highlight.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! highlight.js v9.16.2 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],i=Object.keys,b={},u={},n=/^(no-?highlight|plain|text)$/i,l=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},_="",m={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function C(e){return e.replace(/&/g,"&").replace(//g,">")}function E(e){return e.nodeName.toLowerCase()}function o(e){return n.test(e)}function s(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function g(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),E(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function d(e,n,t){var r=0,a="",i=[];function c(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function o(e){("start"===e.event?u:l)(e.node)}for(;e.length||n.length;){var s=c();if(a+=C(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);o(s.splice(0,1)[0]),(s=c())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(u)}else"start"===s[0].event?i.push(s[0].node):i.pop(),o(s.splice(0,1)[0])}return a+C(t.substr(r))}function R(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return s(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[s(n,{starts:n.starts?s(n.starts):null})]:[n]}function v(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(v)}}function p(n,r){var a={};return"string"==typeof n?t("keyword",n):i(n).forEach(function(e){t(e,n[e])}),a;function t(t,e){r&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n=e.split("|");a[n[0]]=[t,function(e,n){return n?Number(n):function(e){return-1!=c.indexOf(e.toLowerCase())}(e)?0:1}(n[0],n[1])]})}}function O(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,c={},u=[],l={},t=1;function n(e,n){c[t]=e,u.push([e,n]),t+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(n)+1}for(var r=0;r')+n+(t?"":_)}function l(){R+=null!=g.sL?function(){var e="string"==typeof g.sL;if(e&&!b[g.sL])return C(v);var n=e?x(g.sL,v,!0,d[g.sL]):B(v,g.sL.length?g.sL:void 0);return 0")+'"');if("end"===n.type){var r=function(e){var n=e[0],t=c(g,n);if(t){var r=g;for(r.skip?v+=n:(r.rE||r.eE||(v+=n),l(),r.eE&&(v=n));g.cN&&(R+=_),g.skip||g.sL||(p+=g.relevance),(g=g.parent)!==t.parent;);return t.starts&&(t.endSameAsBegin&&(t.starts.eR=t.eR),o(t.starts)),r.rE?0:n.length}}(n);if(null!=r)return r}return v+=t,t.length}var E=S(e);if(!E)throw new Error('Unknown language: "'+e+'"');O(E);var r,g=n||E,d={},R="";for(r=g;r!==E;r=r.parent)r.cN&&(R=u(r.cN,"",!0)+R);var v="",p=0;try{for(var M,N,h=0;g.t.lastIndex=h,M=g.t.exec(a);)N=t(a.substring(h,M.index),M),h=M.index+N;for(t(a.substr(h)),r=g;r.parent;r=r.parent)r.cN&&(R+=_);return{relevance:p,value:R,i:!1,language:e,top:g}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:C(a)};throw e}}function B(t,e){e=e||m.languages||i(b);var r={relevance:0,value:C(t)},a=r;return e.filter(S).filter(T).forEach(function(e){var n=x(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function M(e){return m.tabReplace||m.useBR?e.replace(t,function(e,n){return m.useBR&&"\n"===e?"
":m.tabReplace?n.replace(/\t/g,m.tabReplace):""}):e}function N(e){var n,t,r,a,i,c=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=l.exec(i))return S(t[1])?t[1]:"no-highlight";for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=c?x(c,i,!0):B(i),(t=g(n)).length&&((a=document.createElementNS("http://www.w3.org/1999/xhtml","div")).innerHTML=r.value,r.value=d(t,g(a),i)),r.value=M(r.value),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?u[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}(e.className,c,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,N)}}function S(e){return e=(e||"").toLowerCase(),b[e]||b[u[e]]}function T(e){var n=S(e);return n&&!n.disableAutodetect}return a.highlight=x,a.highlightAuto=B,a.fixMarkup=M,a.highlightBlock=N,a.configure=function(e){m=s(m,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){addEventListener("DOMContentLoaded",h,!1),addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t=b[n]=e(a);v(t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){u[e]=n})},a.listLanguages=function(){return i(b)},a.getLanguage=S,a.autoDetection=T,a.inherit=s,a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},a});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:r},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",n={b:t,e:a,c:["self"]},l=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[n],relevance:10})];return{l:e.UIR,k:{literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstringmodule next pairs pcall print rawequal rawget rawset require select setfenvsetmetatable tonumber tostring type unpack xpcall arg selfcoroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},c:l.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:l}].concat(l)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[n],relevance:5}])}});hljs.registerLanguage("ini",function(e){var b={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",relevance:10},{b:'"""',e:'"""',relevance:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_\.-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_\.-]+/},{b:/=/,eW:!0,relevance:0,c:[e.C(";","$"),e.HCM,{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},b,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}}); \ No newline at end of file diff --git a/docs/theme/highlight.min.js b/docs/theme/highlight.min.js new file mode 100644 index 00000000..cd282cec --- /dev/null +++ b/docs/theme/highlight.min.js @@ -0,0 +1,433 @@ +/*! + Highlight.js v11.5.1 (git: b8f233c8e2) + (c) 2006-2022 Ivan Sagalaev and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";var e={exports:{}};function t(e){ +return e instanceof Map?e.clear=e.delete=e.set=()=>{ +throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n] +;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e} +e.exports=t,e.exports.default=t;var n=e.exports;class i{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function r(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function s(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const o=e=>!!e.kind +;class a{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=r(e)}openNode(e){if(!o(e))return;let t=e.kind +;t=e.sublanguage?"language-"+t:((e,{prefix:t})=>{if(e.includes(".")){ +const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(t,{prefix:this.classPrefix}),this.span(t)}closeNode(e){ +o(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}class c{constructor(){this.rootNode={ +children:[]},this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t={kind:e,children:[]} +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e} +addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} +addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root +;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){ +return new a(this,this.options).value()}finalize(){return!0}}function g(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return f("(?=",e,")")} +function u(e){return f("(?:",e,")*")}function h(e){return f("(?:",e,")?")} +function f(...e){return e.map((e=>g(e))).join("")}function p(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"} +function b(e){return RegExp(e.toString()+"|").exec("").length-1} +const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break} +r+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0], +"("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)} +const x="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",k="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},O={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},N={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},M=(e,t,n={})=>{const i=s({scope:"comment",begin:e,end:t, +contains:[]},n);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const r=p("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:f(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},S=M("//","$"),R=M("/\\*","\\*/"),j=M("#","$");var A=Object.freeze({ +__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:x,UNDERSCORE_IDENT_RE:w, +NUMBER_RE:y,C_NUMBER_RE:_,BINARY_NUMBER_RE:k, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=f(t,/.*\b/,e.binary,/\b.*/)),s({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +BACKSLASH_ESCAPE:v,APOS_STRING_MODE:O,QUOTE_STRING_MODE:N,PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:R,HASH_COMMENT_MODE:j, +NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number", +begin:_,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:k,relevance:0}, +REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, +end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0, +contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:x,relevance:0}, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ +begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ +t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function I(e,t){ +"."===e.input[e.index-1]&&t.ignoreMatch()}function T(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=I,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function B(e,t){ +Array.isArray(e.illegal)&&(e.illegal=p(...e.illegal))}function D(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function H(e,t){ +void 0===e.relevance&&(e.relevance=1)}const P=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=f(n.beforeMatch,d(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},C=["of","and","for","in","not","or","if","then","parent","list","value"] +;function $(e,t,n="keyword"){const i=Object.create(null) +;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,$(e[n],t,n))})),i;function r(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ +return t?Number(t):(e=>C.includes(e.toLowerCase()))(e)?0:1}const z={},K=e=>{ +console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{ +z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) +},G=Error();function Z(e,t,{key:n}){let i=0;const r=e[n],s={},o={} +;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1]) +;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function F(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +G +;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), +G;Z(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +G +;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), +G;Z(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function V(e){ +function t(t,n){ +return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=s(e.classNameAliases||{}),function n(r,o){const a=r +;if(r.isCompiled)return a +;[T,D,F,P].forEach((e=>e(r,o))),e.compilerExtensions.forEach((e=>e(r,o))), +r.__beforeBegin=null,[L,B,H].forEach((e=>e(r,o))),r.isCompiled=!0;let c=null +;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords), +c=r.keywords.$pattern, +delete r.keywords.$pattern),c=c||/\w+/,r.keywords&&(r.keywords=$(r.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +o&&(r.begin||(r.begin=/\B|\b/),a.beginRe=t(a.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), +r.end&&(a.endRe=t(a.end)), +a.terminatorEnd=g(a.end)||"",r.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(r.end?"|":"")+o.terminatorEnd)), +r.illegal&&(a.illegalRe=t(r.illegal)), +r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>s(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?s(e,{ +starts:e.starts?s(e.starts):null +}):Object.isFrozen(e)?s(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{n(e,a) +})),r.starts&&n(r.starts,o),a.matcher=(e=>{const t=new i +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ +return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const Y=r,Q=s,ee=Symbol("nomatch");var te=(e=>{ +const t=Object.create(null),r=Object.create(null),s=[];let o=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let g={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function b(e){ +return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,r=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."), +X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};N("before:highlight",s) +;const o=s.result?s.result:E(s.language,s.code,n) +;return o.code=s.code,N("after:highlight",o),o}function E(e,n,r,s){ +const c=Object.create(null);function l(){if(!O.keywords)return void M.addText(S) +;let e=0;O.keywordPatternRe.lastIndex=0;let t=O.keywordPatternRe.exec(S),n="" +;for(;t;){n+=S.substring(e,t.index) +;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,O.keywords[i]);if(s){ +const[e,i]=s +;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{ +const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0] +;e=O.keywordPatternRe.lastIndex,t=O.keywordPatternRe.exec(S)}var i +;n+=S.substr(e),M.addText(n)}function d(){null!=O.subLanguage?(()=>{ +if(""===S)return;let e=null;if("string"==typeof O.subLanguage){ +if(!t[O.subLanguage])return void M.addText(S) +;e=E(O.subLanguage,S,!0,N[O.subLanguage]),N[O.subLanguage]=e._top +}else e=x(S,O.subLanguage.length?O.subLanguage:null) +;O.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) +})():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){ +if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n] +;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),O=Object.create(e,{parent:{ +value:O}}),O}function f(e,t,n){let r=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,n);if(r){if(e["on:end"]){const n=new i(e) +;e["on:end"](t,n),n.isMatchIgnored&&(r=!1)}if(r){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return f(e.parent,t,n)}function p(e){ +return 0===O.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){ +const t=e[0],i=n.substr(e.index),r=f(O,e,i);if(!r)return ee;const s=O +;O.endScope&&O.endScope._wrap?(d(), +M.addKeyword(t,O.endScope._wrap)):O.endScope&&O.endScope._multi?(d(), +u(O.endScope,e)):s.skip?S+=t:(s.returnEnd||s.excludeEnd||(S+=t), +d(),s.excludeEnd&&(S=t));do{ +O.scope&&M.closeNode(),O.skip||O.subLanguage||(R+=O.relevance),O=O.parent +}while(O!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:t.length} +let m={};function w(t,s){const a=s&&s[0];if(S+=t,null==a)return d(),0 +;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){ +if(S+=n.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=m.rule,t}return 1} +if(m=s,"begin"===s.type)return(e=>{ +const t=e[0],n=e.rule,r=new i(n),s=[n.__beforeBegin,n["on:begin"]] +;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return p(t) +;return n.skip?S+=t:(n.excludeBegin&&(S+=t), +d(),n.returnBegin||n.excludeBegin||(S=t)),h(n,e),n.returnBegin?0:t.length})(s) +;if("illegal"===s.type&&!r){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(O.scope||"")+'"') +;throw e.mode=O,e}if("end"===s.type){const e=b(s);if(e!==ee)return e} +if("illegal"===s.type&&""===a)return 1 +;if(A>1e5&&A>3*s.index)throw Error("potential infinite loop, way more iterations than matches") +;return S+=a,a.length}const y=k(e) +;if(!y)throw K(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const _=V(y);let v="",O=s||_;const N={},M=new g.__emitter(g);(()=>{const e=[] +;for(let t=O;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let S="",R=0,j=0,A=0,I=!1;try{ +for(O.matcher.considerAll();;){ +A++,I?I=!1:O.matcher.considerAll(),O.matcher.lastIndex=j +;const e=O.matcher.exec(n);if(!e)break;const t=w(n.substring(j,e.index),e) +;j=e.index+t}return w(n.substr(j)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{ +language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:O}}catch(t){ +if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n), +illegal:!0,relevance:0,_illegalBy:{message:t.message,index:j, +context:n.slice(j-100,j+100),mode:t.mode,resultSoFar:v},_emitter:M};if(o)return{ +language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:O} +;throw t}}function x(e,n){n=n||g.languages||Object.keys(t);const i=(e=>{ +const t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)} +;return t._emitter.addText(e),t})(e),r=n.filter(k).filter(O).map((t=>E(t,e,!1))) +;r.unshift(i);const s=r.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(k(e.language).supersetOf===t.language)return 1 +;if(k(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o +;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=g.languageDetectRe.exec(t);if(n){const t=k(n[1]) +;return t||(W(a.replace("{}",n[1])), +W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||k(e)))})(e);if(b(n))return +;if(N("before:highlightElement",{el:e,language:n +}),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),g.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,s.language),e.result={language:s.language,re:s.relevance, +relevance:s.relevance},s.secondBest&&(e.secondBest={ +language:s.secondBest.language,relevance:s.secondBest.relevance +}),N("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0 +}function k(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +r[e.toLowerCase()]=t}))}function O(e){const t=k(e) +;return t&&!t.disableAutodetect}function N(e,t){const n=e;s.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(e,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"), +X("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=Q(g,e)}, +initHighlighting:()=>{ +_(),X("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),X("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(n,i)=>{let r=null;try{r=i(e)}catch(e){ +if(K("Language definition for '{}' could not be registered.".replace("{}",n)), +!o)throw e;K(e),r=c} +r.name||(r.name=n),t[n]=r,r.rawDefinition=i.bind(null,e),r.aliases&&v(r.aliases,{ +languageName:n})},unregisterLanguage:e=>{delete t[e] +;for(const t of Object.keys(r))r[t]===e&&delete r[t]}, +listLanguages:()=>Object.keys(t),getLanguage:k,registerAliases:v, +autoDetection:O,inherit:Q,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} +}),e.debugMode=()=>{o=!1},e.safeMode=()=>{o=!0 +},e.versionString="11.5.1",e.regex={concat:f,lookahead:d,either:p,optional:h, +anyNumberOfTimes:u};for(const e in A)"object"==typeof A[e]&&n(A[e]) +;return Object.assign(e,A),e})({});return te}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `ini` grammar compiled for Highlight.js 11.5.1 */ +(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,a={className:"number", +relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{begin:e.NUMBER_RE}] +},s=e.COMMENT();s.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const i={ +className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/ +}]},t={className:"literal",begin:/\bon|off|true|false|yes|no\b/},r={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''", +end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"' +},{begin:"'",end:"'"}]},l={begin:/\[/,end:/\]/,contains:[s,t,i,r,a,"self"], +relevance:0},c=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ +name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, +contains:[s,{className:"section",begin:/\[+/,end:/\]+/},{ +begin:n.concat(c,"(\\s*\\.\\s*",c,")*",n.lookahead(/\s*=\s*[^#\s]/)), +className:"attr",starts:{end:/$/,contains:[s,l,t,i,r,a]}}]}}})() +;hljs.registerLanguage("ini",e)})();/*! `lua` grammar compiled for Highlight.js 11.5.1 */ +(()=>{var e=(()=>{"use strict";return e=>{const t="\\[=*\\[",a="\\]=*\\]",n={ +begin:t,end:a,contains:["self"] +},o=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[",a,{contains:[n], +relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE, +literal:"true false nil", +keyword:"and break do else elseif end for goto if in local not or repeat return then until while", +built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove" +},contains:o.concat([{className:"function",beginKeywords:"function",end:"\\)", +contains:[e.inherit(e.TITLE_MODE,{ +begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params", +begin:"\\(",endsWithParent:!0,contains:o}].concat(o) +},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string", +begin:t,end:a,contains:[n],relevance:5}])}}})();hljs.registerLanguage("lua",e) +})();/*! `rust` grammar compiled for Highlight.js 11.5.1 */ +(()=>{var e=(()=>{"use strict";return e=>{const t=e.regex,n={ +className:"title.function.invoke",relevance:0, +begin:t.concat(/\b/,/(?!let\b)/,e.IDENT_RE,t.lookahead(/\s*\(/)) +},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","panic!","file!","format!","format_args!","include_bin!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"] +;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?", +type:["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"], +keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], +literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},n]}}})() +;hljs.registerLanguage("rust",e)})();/*! `ruby` grammar compiled for Highlight.js 11.5.1 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n=e.regex,a="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",s=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(s,/(::\w+)*/),r={ +"variable.constant":["__FILE__","__LINE__"], +"variable.language":["self","super"], +keyword:["alias","and","attr_accessor","attr_reader","attr_writer","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","include","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield"], +built_in:["proc","lambda"],literal:["true","false","nil"]},c={ +className:"doctag",begin:"@[A-Za-z]+"},t={begin:"#<",end:">" +},b=[e.COMMENT("#","$",{contains:[c]}),e.COMMENT("^=begin","^=end",{ +contains:[c],relevance:10}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],l={ +className:"subst",begin:/#\{/,end:/\}/,keywords:r},d={className:"string", +contains:[e.BACKSLASH_ESCAPE,l],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/, +end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{ +begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/, +end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{ +begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ +begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ +begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ +begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)), +contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, +contains:[e.BACKSLASH_ESCAPE,l]})]}]},g="[0-9](_?[0-9])*",o={className:"number", +relevance:0,variants:[{ +begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{ +begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" +},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ +begin:"\\b0(_?[0-7])+r?i?\\b"}]},_={variants:[{match:/\(\)/},{ +className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0, +keywords:r}]},u=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{ +match:[/class\s+/,i]}],scope:{2:"title.class",4:"title.class.inherited"}, +keywords:r},{relevance:0,match:[i,/\.new[ (]/],scope:{1:"title.class"}},{ +relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"},{ +match:[/def/,/\s+/,a],scope:{1:"keyword",3:"title.function"},contains:[_]},{ +begin:e.IDENT_RE+"::"},{className:"symbol", +begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", +begin:":(?!\\s)",contains:[d,{begin:a}],relevance:0},o,{className:"variable", +begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ +className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0, +relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", +keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,l], +illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{ +begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[", +end:"\\][a-z]*"}]}].concat(t,b),relevance:0}].concat(t,b) +;l.contains=u,_.contains=u;const w=[{begin:/^\s*=>/,starts:{end:"$",contains:u} +},{className:"meta.prompt", +begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", +starts:{end:"$",keywords:r,contains:u}}];return b.unshift(t),{name:"Ruby", +aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/, +contains:[e.SHEBANG({binary:"ruby"})].concat(w).concat(b).concat(u)}}})() +;hljs.registerLanguage("ruby",e)})();/*! `yaml` grammar compiled for Highlight.js 11.5.1 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, +end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", +contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", +begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] +;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})(); \ No newline at end of file diff --git a/selene-lib/Cargo.toml b/selene-lib/Cargo.toml index a61689f0..03bcd6e6 100644 --- a/selene-lib/Cargo.toml +++ b/selene-lib/Cargo.toml @@ -7,7 +7,7 @@ description = "A library for linting Lua code. You probably want selene instead. categories = ["command-line-utilities"] keywords = ["lua", "linter", "lua51"] repository = "https://github.com/Kampfkarren/selene" -edition = "2018" +edition = "2021" [dependencies] codespan = "0.9" @@ -20,6 +20,7 @@ paste = "1.0" rbx_reflection = { version = "3", optional = true } regex = "1" serde = "1.0" +serde_yaml = "0.8.24" toml = "0.5" [dev-dependencies] diff --git a/selene-lib/default_std/lua51.toml b/selene-lib/default_std/lua51.toml deleted file mode 100644 index 9f818a79..00000000 --- a/selene-lib/default_std/lua51.toml +++ /dev/null @@ -1,725 +0,0 @@ -# globals -[_G] -property = true -writable = "new-fields" - -[_VERSION] -property = true - -[arg] -property = true -writable = "new-fields" - -[[assert.args]] -type = "any" - -[[assert.args]] -type = "string" -required = "A failed assertion without a message is unhelpful to users." - -[[collectgarbage.args]] -type = [ - "collect", - "count", - "restart", - "setpause", - "setstepmul", - "step", - "stop", -] -required = false - -[[dofile.args]] -type = "string" -required = false - -[[error.args]] -type = "string" -required = "Erroring without a message is unhelpful to users." - -[[error.args]] -type = "number" -required = false - -[[getfenv.args]] -type = "any" # TODO: function | number -required = false - -[[getmetatable.args]] -type = "table" - -[[ipairs.args]] -type = "table" - -[[load.args]] -type = "function" - -[[load.args]] -type = "string" -required = false - -[[loadfile.args]] -type = "string" -required = false - -[[loadstring.args]] -type = "string" - -[[loadstring.args]] -type = "string" -required = false - -[[module.args]] -type = "string" - -[[module.args]] -type = "..." - -[[newproxy.args]] -type = "bool" -required = false - -[[next.args]] -type = "table" - -[[next.args]] -type = "number" -required = false - -[[pairs.args]] -type = "table" - -[[pcall.args]] -type = "function" - -[[pcall.args]] -type = "..." -required = false - -[[print.args]] -type = "..." -required = false - -[[rawequal.args]] -type = "any" - -[[rawequal.args]] -type = "any" - -[[rawget.args]] -type = "any" - -[[rawget.args]] -type = "any" - -[[rawset.args]] -type = "any" - -[[rawset.args]] -type = "any" - -[[rawset.args]] -type = "any" - -[[require.args]] -type = "string" - -[[select.args]] -type = "any" # TODO: "#" | number - -[[select.args]] -type = "..." -required = true - -[[setfenv.args]] -type = "any" # TODO: Union type, function or number - -[[setfenv.args]] -type = "table" - -[[setmetatable.args]] -type = "table" - -[[setmetatable.args]] -type = "table" -required = false - -[[tonumber.args]] -type = "any" - -[[tonumber.args]] -type = "number" -required = false - -[[tostring.args]] -type = "any" - -[[type.args]] -type = "any" - -[[unpack.args]] -type = "table" - -[[unpack.args]] -type = "number" -required = false - -[[unpack.args]] -type = "number" -required = false - -[[xpcall.args]] -type = "function" - -[[xpcall.args]] -type = "..." -required = false - -# coroutine -[[coroutine.create.args]] -type = "function" - -[[coroutine.resume.args]] -type = { display = "coroutine" } - -[[coroutine.resume.args]] -type = "..." -required = false - -[coroutine.running] -args = [] - -[[coroutine.status.args]] -type = { display = "coroutine" } - -[[coroutine.wrap.args]] -type = "function" - -[[coroutine.yield.args]] -type = "..." -required = false - -# debug -[debug.debug] -args = [] - -# This can be used with userdata and thread too, but we don't infer those yet -[[debug.getfenv.args]] -type = "function" - -[[debug.gethook.args]] -type = "any" -required = false - -# debug.getinfo and friends has optional parameters *behind* it, which are unsupported -# and are instead just untyped -[[debug.getinfo.args]] -type = "any" - -[[debug.getinfo.args]] -type = "any" -required = false # As mentioned above, since only one of the three arguments is required, this is necessary - -[[debug.getinfo.args]] -type = "any" -required = false - -[[debug.getlocal.args]] -type = "any" - -[[debug.getlocal.args]] -type = "any" - -[[debug.getlocal.args]] -type = "any" -required = false - -[[debug.getmetatable.args]] -type = "any" - -[debug.getregistry] -args = [] - -[[debug.getupvalue.args]] -type = "function" - -[[debug.getupvalue.args]] -type = "number" - -[[debug.setfenv.args]] -type = "any" - -[[debug.setfenv.args]] -type = "table" - -[[debug.sethook.args]] -type = "any" - -[[debug.sethook.args]] -type = "any" - -[[debug.sethook.args]] -type = "any" -required = false - -[[debug.sethook.args]] -type = "any" -required = false - -[[debug.setlocal.args]] -type = "any" - -[[debug.setlocal.args]] -type = "any" - -[[debug.setlocal.args]] -type = "any" - -[[debug.setlocal.args]] -type = "any" -required = false - -[[debug.setmetatable.args]] -type = "any" - -[[debug.setmetatable.args]] -type = "table" - -[[debug.setupvalue.args]] -type = "function" - -[[debug.setupvalue.args]] -type = "number" - -[[debug.setupvalue.args]] -type = "string" - -# Since all of the arguments are optional and disregard order, the best thing to do is make them untyped. -[[debug.traceback.args]] -type = "any" -required = false - -[[debug.traceback.args]] -type = "any" -required = false - -[[debug.traceback.args]] -type = "any" -required = false - -# io -[[io.close.args]] -type = { display = "file" } -required = false - -[io.flush] -args = [] - -[[io.input.args]] -type = { display = "file" } -required = false - -[[io.lines.args]] -type = "string" - -[[io.open.args]] -type = "string" - -[[io.open.args]] -type = [ - "r", "rb", - "w", "wb", - "a", "ab", - "r+", "rb+", - "w+", "wb+", - "a+", "ab+", -] -required = false - -[[io.output.args]] -type = { display = "file" } -required = false - -[[io.popen.args]] -type = "string" - -[[io.popen.args]] -type = [ - "r", "rb", - "w", "wb", - "a", "ab", - "r+", "rb+", - "w+", "wb+", - "a+", "ab+", -] -required = false - -[[io.read.args]] -type = "..." # TODO: Union type, constant string or number - -[io.stderr] -property = true - -[io.stdin] -property = true - -[io.stdout] -property = true - -[io.tmpfile] -args = [] - -[[io.type.args]] -type = { display = "potentially file-like object" } - -[[io.write.args]] -type = "..." - -# math -[math.huge] -property = true - -[math.pi] -property = true - -[[math.abs.args]] -type = "number" - -[[math.acos.args]] -type = "number" - -[[math.asin.args]] -type = "number" - -[[math.atan.args]] -type = "number" - -[[math.atan2.args]] -type = "number" - -[[math.atan2.args]] -type = "number" - -[[math.ceil.args]] -type = "number" - -[[math.cos.args]] -type = "number" - -[[math.cosh.args]] -type = "number" - -[[math.deg.args]] -type = "number" - -[[math.exp.args]] -type = "number" - -[[math.floor.args]] -type = "number" - -[[math.fmod.args]] -type = "number" - -[[math.fmod.args]] -type = "number" - -[[math.frexp.args]] -type = "number" - -[[math.ldexp.args]] -type = "number" - -[[math.ldexp.args]] -type = "number" - -[[math.log.args]] -type = "number" - -[[math.log10.args]] -type = "number" - -[[math.max.args]] -type = "number" - -[[math.max.args]] -type = "..." -required = "use of max only makes sense with more than 1 parameter" - -[[math.min.args]] -type = "number" - -[[math.min.args]] -type = "..." -required = "use of min only makes sense with more than 1 parameter" - -[[math.modf.args]] -type = "number" - -[[math.pow.args]] -type = "number" - -[[math.pow.args]] -type = "number" - -[[math.rad.args]] -type = "number" - -[[math.random.args]] -type = "number" -required = false - -[[math.random.args]] -type = "number" -required = false - -[[math.randomseed.args]] -type = "number" - -[[math.sin.args]] -type = "number" - -[[math.sinh.args]] -type = "number" - -[[math.sqrt.args]] -type = "number" - -[[math.tan.args]] -type = "number" - -[[math.tanh.args]] -type = "number" - -# os -[os.clock] -args = [] - -[[os.date.args]] -type = "string" -required = false - -[[os.date.args]] -type = "number" -required = false - -[[os.difftime.args]] -type = "number" - -[[os.difftime.args]] -type = "number" - -[[os.execute.args]] -type = "string" -required = false - -[[os.exit.args]] -type = "number" -required = false - -[[os.getenv.args]] -type = "string" - -[[os.remove.args]] -type = "string" - -[[os.rename.args]] -type = "string" - -[[os.rename.args]] -type = "string" - -[[os.setlocale.args]] -type = "string" - -[[os.setlocale.args]] -type = [ - "all", - "collate", - "ctype", - "monetary", - "numeric", - "time", -] -required = false - -[[os.time.args]] -type = "table" -required = false - -[os.tmpname] -args = [] - -# package -[package.cpath] -property = true -writable = "full" - -[package.loaded] -property = true -writable = "new-fields" - -[package.loaders] -property = true -writable = "new-fields" - -[[package.loadlib.args]] -type = "string" - -[[package.loadlib.args]] -type = "string" - -[package.path] -property = true -writable = "full" - -[package.preload] -property = true -writable = "new-fields" - -[[package.seeall.args]] -type = "table" - -# string -[[string.byte.args]] -type = "string" - -[[string.byte.args]] -type = "number" -required = false - -[[string.byte.args]] -type = "number" -required = false - -[[string.char.args]] -type = "number" -required = "string.char should be used with an argument despite it not throwing" - -[[string.char.args]] -type = "..." -required = false - -[[string.dump.args]] -type = "function" - -[[string.find.args]] -type = "string" - -[[string.find.args]] -type = "string" # TODO: Pattern type? - -[[string.find.args]] -type = "number" -required = false - -[[string.find.args]] -type = "bool" -required = false - -[[string.format.args]] -type = "string" - -[[string.format.args]] -type = "..." -required = "string.format should only be used for strings that need formatting" - -[[string.gmatch.args]] -type = "string" - -[[string.gmatch.args]] -type = "string" - -[[string.gsub.args]] -type = "string" - -[[string.gsub.args]] -type = "string" # TODO: Pattern type? - -[[string.gsub.args]] -type = "any" - -[[string.gsub.args]] -type = "number" -required = false - -[[string.len.args]] -type = "string" - -[[string.lower.args]] -type = "string" - -[[string.match.args]] -type = "string" - -[[string.match.args]] -type = "string" # TODO: Pattern type? - -[[string.match.args]] -type = "number" -required = false - -[[string.rep.args]] -type = "string" - -[[string.rep.args]] -type = "number" - -[[string.reverse.args]] -type = "string" - -[[string.sub.args]] -type = "string" - -[[string.sub.args]] -type = "number" - -[[string.sub.args]] -type = "number" -required = false - -[[string.upper.args]] -type = "string" - -# table -[[table.concat.args]] -type = "table" - -[[table.concat.args]] -type = "string" -required = false - -[[table.concat.args]] -type = "number" -required = false - -[[table.concat.args]] -type = "number" -required = false - -[[table.insert.args]] -type = "table" - -[[table.insert.args]] -type = "any" - -[[table.insert.args]] -type = "any" -required = false - -[[table.maxn.args]] -type = "table" - -[[table.remove.args]] -type = "table" - -[[table.remove.args]] -type = "number" -required = false - -[[table.sort.args]] -type = "table" - -[[table.sort.args]] -type = "function" -required = false diff --git a/selene-lib/default_std/lua51.yml b/selene-lib/default_std/lua51.yml new file mode 100644 index 00000000..bf242244 --- /dev/null +++ b/selene-lib/default_std/lua51.yml @@ -0,0 +1,555 @@ +--- +globals: + _G: + property: new-fields + _VERSION: + property: read-only + arg: + property: new-fields + assert: + args: + - type: any + - required: A failed assertion without a message is unhelpful to users. + type: string + collectgarbage: + args: + - required: false + type: + - collect + - count + - restart + - setpause + - setstepmul + - step + - stop + coroutine.create: + args: + - type: function + coroutine.resume: + args: + - type: + display: coroutine + - required: false + type: "..." + coroutine.running: + args: [] + coroutine.status: + args: + - type: + display: coroutine + coroutine.wrap: + args: + - type: function + coroutine.yield: + args: + - required: false + type: "..." + debug.debug: + args: [] + debug.getfenv: + args: + - type: function + debug.gethook: + args: + - required: false + type: any + debug.getinfo: + args: + - type: any + - required: false + type: any + - required: false + type: any + debug.getlocal: + args: + - type: any + - type: any + - required: false + type: any + debug.getmetatable: + args: + - type: any + debug.getregistry: + args: [] + debug.getupvalue: + args: + - type: function + - type: number + debug.setfenv: + args: + - type: any + - type: table + debug.sethook: + args: + - type: any + - type: any + - required: false + type: any + - required: false + type: any + debug.setlocal: + args: + - type: any + - type: any + - type: any + - required: false + type: any + debug.setmetatable: + args: + - type: any + - type: table + debug.setupvalue: + args: + - type: function + - type: number + - type: string + debug.traceback: + args: + - required: false + type: any + - required: false + type: any + - required: false + type: any + dofile: + args: + - required: false + type: string + error: + args: + - required: Erroring without a message is unhelpful to users. + type: string + - required: false + type: number + getfenv: + args: + - required: false + type: any + getmetatable: + args: + - type: table + io.close: + args: + - required: false + type: + display: file + io.flush: + args: [] + io.input: + args: + - required: false + type: + display: file + io.lines: + args: + - type: string + io.open: + args: + - type: string + - required: false + type: + - r + - rb + - w + - wb + - a + - ab + - r+ + - rb+ + - w+ + - wb+ + - a+ + - ab+ + io.output: + args: + - required: false + type: + display: file + io.popen: + args: + - type: string + - required: false + type: + - r + - rb + - w + - wb + - a + - ab + - r+ + - rb+ + - w+ + - wb+ + - a+ + - ab+ + io.read: + args: + - type: "..." + io.stderr: + property: read-only + io.stdin: + property: read-only + io.stdout: + property: read-only + io.tmpfile: + args: [] + io.type: + args: + - type: + display: potentially file-like object + io.write: + args: + - type: "..." + ipairs: + args: + - type: table + load: + args: + - type: function + - required: false + type: string + loadfile: + args: + - required: false + type: string + loadstring: + args: + - type: string + - required: false + type: string + math.abs: + args: + - type: number + math.acos: + args: + - type: number + math.asin: + args: + - type: number + math.atan: + args: + - type: number + math.atan2: + args: + - type: number + - type: number + math.ceil: + args: + - type: number + math.cos: + args: + - type: number + math.cosh: + args: + - type: number + math.deg: + args: + - type: number + math.exp: + args: + - type: number + math.floor: + args: + - type: number + math.fmod: + args: + - type: number + - type: number + math.frexp: + args: + - type: number + math.huge: + property: read-only + math.ldexp: + args: + - type: number + - type: number + math.log: + args: + - type: number + math.log10: + args: + - type: number + math.max: + args: + - type: number + - required: use of max only makes sense with more than 1 parameter + type: "..." + math.min: + args: + - type: number + - required: use of min only makes sense with more than 1 parameter + type: "..." + math.modf: + args: + - type: number + math.pi: + property: read-only + math.pow: + args: + - type: number + - type: number + math.rad: + args: + - type: number + math.random: + args: + - required: false + type: number + - required: false + type: number + math.randomseed: + args: + - type: number + math.sin: + args: + - type: number + math.sinh: + args: + - type: number + math.sqrt: + args: + - type: number + math.tan: + args: + - type: number + math.tanh: + args: + - type: number + module: + args: + - type: string + - type: "..." + newproxy: + args: + - required: false + type: bool + next: + args: + - type: table + - required: false + type: number + os.clock: + args: [] + os.date: + args: + - required: false + type: string + - required: false + type: number + os.difftime: + args: + - type: number + - type: number + os.execute: + args: + - required: false + type: string + os.exit: + args: + - required: false + type: number + os.getenv: + args: + - type: string + os.remove: + args: + - type: string + os.rename: + args: + - type: string + - type: string + os.setlocale: + args: + - type: string + - required: false + type: + - all + - collate + - ctype + - monetary + - numeric + - time + os.time: + args: + - required: false + type: table + os.tmpname: + args: [] + package.cpath: + property: full-write + package.loaded: + property: new-fields + package.loaders: + property: new-fields + package.loadlib: + args: + - type: string + - type: string + package.path: + property: full-write + package.preload: + property: new-fields + package.seeall: + args: + - type: table + pairs: + args: + - type: table + pcall: + args: + - type: function + - required: false + type: "..." + print: + args: + - required: false + type: "..." + rawequal: + args: + - type: any + - type: any + rawget: + args: + - type: any + - type: any + rawset: + args: + - type: any + - type: any + - type: any + require: + args: + - type: string + select: + args: + - type: any + - type: "..." + setfenv: + args: + - type: any + - type: table + setmetatable: + args: + - type: table + - required: false + type: table + string.byte: + args: + - type: string + - required: false + type: number + - required: false + type: number + string.char: + args: + - required: string.char should be used with an argument despite it not throwing + type: number + - required: false + type: "..." + string.dump: + args: + - type: function + string.find: + args: + - type: string + - type: string + - required: false + type: number + - required: false + type: bool + string.format: + args: + - type: string + - required: string.format should only be used for strings that need formatting + type: "..." + string.gmatch: + args: + - type: string + - type: string + string.gsub: + args: + - type: string + - type: string + - type: any + - required: false + type: number + string.len: + args: + - type: string + string.lower: + args: + - type: string + string.match: + args: + - type: string + - type: string + - required: false + type: number + string.rep: + args: + - type: string + - type: number + string.reverse: + args: + - type: string + string.sub: + args: + - type: string + - type: number + - required: false + type: number + string.upper: + args: + - type: string + table.concat: + args: + - type: table + - required: false + type: string + - required: false + type: number + - required: false + type: number + table.insert: + args: + - type: table + - type: any + - required: false + type: any + table.maxn: + args: + - type: table + table.remove: + args: + - type: table + - required: false + type: number + table.sort: + args: + - type: table + - required: false + type: function + tonumber: + args: + - type: any + - required: false + type: number + tostring: + args: + - type: any + type: + args: + - type: any + unpack: + args: + - type: table + - required: false + type: number + - required: false + type: number + xpcall: + args: + - type: function + - required: false + type: "..." diff --git a/selene-lib/default_std/lua52.toml b/selene-lib/default_std/lua52.toml deleted file mode 100644 index 679f27cb..00000000 --- a/selene-lib/default_std/lua52.toml +++ /dev/null @@ -1,19 +0,0 @@ -[selene] -base = "lua51" - -[getfenv] -removed = true - -[setfenv] -removed = true - -[[table.unpack.args]] -type = "table" - -[[table.unpack.args]] -type = "number" -required = false - -[[table.unpack.args]] -type = "number" -required = false diff --git a/selene-lib/default_std/lua52.yml b/selene-lib/default_std/lua52.yml new file mode 100644 index 00000000..90ef5d8e --- /dev/null +++ b/selene-lib/default_std/lua52.yml @@ -0,0 +1,14 @@ +--- +base: lua51 +globals: + getfenv: + removed: true + setfenv: + removed: true + table.unpack: + args: + - type: table + - required: false + type: number + - required: false + type: number diff --git a/selene-lib/default_std/lua53.toml b/selene-lib/default_std/lua53.toml deleted file mode 100644 index 7c4cb721..00000000 --- a/selene-lib/default_std/lua53.toml +++ /dev/null @@ -1,18 +0,0 @@ -[selene] -base = "lua52" - -[[math.log.args]] -type = "number" - -[[math.log.args]] -type = "number" -required = false - -[[string.pack.args]] -type = "string" - -[[string.packsize.args]] -type = "string" - -[[string.unpack.args]] -type = "string" diff --git a/selene-lib/default_std/lua53.yml b/selene-lib/default_std/lua53.yml new file mode 100644 index 00000000..936f4653 --- /dev/null +++ b/selene-lib/default_std/lua53.yml @@ -0,0 +1,17 @@ +--- +base: lua52 +globals: + math.log: + args: + - type: number + - required: false + type: number + string.pack: + args: + - type: string + string.packsize: + args: + - type: string + string.unpack: + args: + - type: string diff --git a/selene-lib/src/lib.rs b/selene-lib/src/lib.rs index fc8986d0..4a9a2ecb 100644 --- a/selene-lib/src/lib.rs +++ b/selene-lib/src/lib.rs @@ -56,10 +56,15 @@ impl Error for CheckerError {} #[derive(Deserialize)] #[serde(default)] +#[serde(rename_all = "kebab-case")] pub struct CheckerConfig { pub config: HashMap, pub rules: HashMap, pub std: String, + + // Not locked behind Roblox feature so that selene.toml for Roblox will + // run even without it. + pub roblox_std_source: RobloxStdSource, } // #[derive(Default)] cannot be used since it binds V to Default @@ -69,6 +74,7 @@ impl Default for CheckerConfig { config: HashMap::new(), rules: HashMap::new(), std: "lua51".to_owned(), + roblox_std_source: RobloxStdSource::default(), } } } @@ -91,6 +97,19 @@ impl RuleVariation { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum RobloxStdSource { + Floating, + Pinned, +} + +impl Default for RobloxStdSource { + fn default() -> Self { + Self::Floating + } +} + macro_rules! use_rules { { $( diff --git a/selene-lib/src/rules.rs b/selene-lib/src/rules.rs index 48768bf5..98121324 100644 --- a/selene-lib/src/rules.rs +++ b/selene-lib/src/rules.rs @@ -205,11 +205,7 @@ pub struct Context { impl Context { #[cfg(feature = "roblox")] pub fn is_roblox(&self) -> bool { - if let Some(ref meta) = self.standard_library.meta { - meta.name == Some("roblox".to_owned()) - } else { - false - } + self.standard_library.name.as_deref() == Some("roblox") } #[cfg(not(feature = "roblox"))] diff --git a/selene-lib/src/rules/standard_library.rs b/selene-lib/src/rules/standard_library.rs index db6ebf78..174d156c 100644 --- a/selene-lib/src/rules/standard_library.rs +++ b/selene-lib/src/rules/standard_library.rs @@ -250,12 +250,12 @@ impl StandardLibraryVisitor<'_> { mut name_path: Vec, range: (Position, Position), ) { + // Make sure it's not just `bad()`, and that it's not a field access from a global outside of standard library if self.standard_library.find_global(&name_path).is_none() - && self + && !self .standard_library - .find_global(&[name_path[0].to_owned()]) - .is_some() - // Make sure it's not just `bad()` + .get_globals_under(&name_path[0]) + .is_empty() { let field = name_path.pop().unwrap(); assert!(!name_path.is_empty(), "name_path is empty"); @@ -265,11 +265,13 @@ impl StandardLibraryVisitor<'_> { let path = &name_path[0..bound]; match self.standard_library.find_global(path) { Some(field) => { - match field { - Field::Any => return, + match field.field_kind { + FieldKind::Any => return, - Field::Property { writable } => { - if writable.is_some() && *writable != Some(Writable::Overridden) { + FieldKind::Property(writability) => { + if writability != PropertyWritability::ReadOnly + && writability != PropertyWritability::OverrideFields + { return; } } @@ -327,15 +329,15 @@ impl Visitor for StandardLibraryVisitor<'_> { { match self.standard_library.find_global(&name_path) { Some(field) => { - match field { - Field::Property { writable } => { - if writable.is_some() - && *writable != Some(Writable::NewFields) + match field.field_kind { + FieldKind::Property(writability) => { + if writability != PropertyWritability::ReadOnly + && writability != PropertyWritability::NewFields { continue; } } - Field::Any => continue, + FieldKind::Any => continue, _ => {} }; @@ -367,13 +369,15 @@ impl Visitor for StandardLibraryVisitor<'_> { let name = name_token.token().to_string(); if let Some(global) = self.standard_library.find_global(&[name.to_owned()]) { - match global { - Field::Property { writable } => { - if writable.is_some() && *writable != Some(Writable::NewFields) { + match global.field_kind { + FieldKind::Property(writability) => { + if writability != PropertyWritability::ReadOnly + && writability != PropertyWritability::NewFields + { continue; } } - Field::Any => continue, + FieldKind::Any => continue, _ => {} }; @@ -454,12 +458,9 @@ impl Visitor for StandardLibraryVisitor<'_> { } }; - let (arguments, expecting_method) = match &field { - standard_library::Field::Any => return, - standard_library::Field::Complex { - function: Some(function), - .. - } => (&function.arguments, &function.method), + let function = match &field.field_kind { + FieldKind::Any => return, + FieldKind::Function(function) => function, _ => { self.diagnostics.push(Diagnostic::new( "incorrect_standard_library_use", @@ -484,7 +485,7 @@ impl Visitor for StandardLibraryVisitor<'_> { _ => unreachable!("function_call.call_suffix != ast::Suffix::Call"), }; - if *expecting_method != call_is_method { + if function.method != call_is_method { let problem = if call_is_method { "is not a method" } else { @@ -545,13 +546,14 @@ impl Visitor for StandardLibraryVisitor<'_> { _ => {} } - let mut expected_args = arguments + let mut expected_args = function + .arguments .iter() .filter(|arg| arg.required != Required::NotRequired) .count(); let mut vararg = false; - let mut max_args = arguments.len(); + let mut max_args = function.arguments.len(); let mut maybe_more_arguments = false; @@ -577,11 +579,11 @@ impl Visitor for StandardLibraryVisitor<'_> { } }; - if let Some(last) = arguments.last() { + if let Some(last) = function.arguments.last() { if last.argument_type == ArgumentType::Vararg { if let Required::Required(message) = &last.required { // Functions like math.ceil where not using the vararg is wrong - if arguments.len() > argument_types.len() && !maybe_more_arguments { + if function.arguments.len() > argument_types.len() && !maybe_more_arguments { self.diagnostics.push(Diagnostic::new_complete( "incorrect_standard_library_use", format!( @@ -620,7 +622,8 @@ impl Visitor for StandardLibraryVisitor<'_> { )); } - for ((range, passed_type), expected) in argument_types.iter().zip(arguments.iter()) { + for ((range, passed_type), expected) in argument_types.iter().zip(function.arguments.iter()) + { if expected.argument_type == ArgumentType::Vararg { continue; } diff --git a/selene-lib/src/rules/test_util.rs b/selene-lib/src/rules/test_util.rs index b74bb2d9..9883864e 100644 --- a/selene-lib/src/rules/test_util.rs +++ b/selene-lib/src/rules/test_util.rs @@ -1,5 +1,5 @@ use super::{Context, Rule}; -use crate::{test_util::PrettyString, StandardLibrary}; +use crate::{standard_library::v1, test_util::PrettyString, StandardLibrary}; use std::{ fs, io::Write, @@ -44,9 +44,10 @@ pub fn test_lint_config< let path_base = TEST_PROJECTS_ROOT.join(lint_name).join(test_name); if let Ok(test_std_contents) = fs::read_to_string(path_base.with_extension("std.toml")) { - let mut std: StandardLibrary = toml::from_str(&test_std_contents).unwrap(); - std.inflate(); - config.standard_library = std; + // config.standard_library = toml::from_str(&test_std_contents).unwrap(); + config.standard_library = toml::from_str::(&test_std_contents) + .unwrap() + .into(); } let lua_source = diff --git a/selene-lib/src/rules/undefined_variable.rs b/selene-lib/src/rules/undefined_variable.rs index 83b76b22..0a62444e 100644 --- a/selene-lib/src/rules/undefined_variable.rs +++ b/selene-lib/src/rules/undefined_variable.rs @@ -30,10 +30,10 @@ impl Rule for UndefinedVariableLint { && reference.read && !read.contains(&reference.identifier) && !is_valid_vararg_reference(&scope_manager, reference) - && !context + && context .standard_library - .globals - .contains_key(&reference.name) + .get_globals_under(&reference.name) + .is_empty() { read.insert(reference.identifier); diff --git a/selene-lib/src/rules/unscoped_variables.rs b/selene-lib/src/rules/unscoped_variables.rs index 4cc569a5..855f5289 100644 --- a/selene-lib/src/rules/unscoped_variables.rs +++ b/selene-lib/src/rules/unscoped_variables.rs @@ -46,10 +46,10 @@ impl Rule for UnscopedVariablesLint { && reference.write && !read.contains(&reference.identifier) && !self.ignore_pattern.is_match(&reference.name) - && !context + && context .standard_library - .globals - .contains_key(&reference.name) + .get_globals_under(&reference.name) + .is_empty() { read.insert(reference.identifier); diff --git a/selene-lib/src/standard_library/mod.rs b/selene-lib/src/standard_library/mod.rs new file mode 100644 index 00000000..5af2d7c3 --- /dev/null +++ b/selene-lib/src/standard_library/mod.rs @@ -0,0 +1,598 @@ +pub mod v1; +mod v1_upgrade; + +use std::{ + collections::{BTreeMap, HashMap}, + fmt, io, +}; + +use serde::{ + de::{self, Deserializer, Visitor}, + ser::{SerializeMap, SerializeSeq, Serializer}, + Deserialize, Serialize, +}; + +lazy_static::lazy_static! { + static ref ANY_TABLE: BTreeMap = { + let mut map = BTreeMap::new(); + map.insert("*".to_owned(), Field { + field_kind: FieldKind::Any, + }); + map + }; +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] +pub struct StandardLibrary { + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub base: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub globals: BTreeMap, + + #[serde(default)] + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub structs: BTreeMap>, + + /// Internal, used for the Roblox standard library + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub last_updated: Option, +} + +#[derive(Debug)] +pub enum StandardLibraryError { + DeserializeTomlError(toml::de::Error), + DeserializeYamlError(serde_yaml::Error), + IoError(io::Error), +} + +impl fmt::Display for StandardLibraryError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + StandardLibraryError::DeserializeTomlError(error) => { + write!(formatter, "deserialize toml error: {}", error) + } + + StandardLibraryError::DeserializeYamlError(error) => { + write!(formatter, "deserialize yaml error: {}", error) + } + + StandardLibraryError::IoError(error) => write!(formatter, "io error: {}", error), + } + } +} + +impl std::error::Error for StandardLibraryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use StandardLibraryError::*; + + match self { + DeserializeTomlError(error) => Some(error), + DeserializeYamlError(error) => Some(error), + IoError(error) => Some(error), + } + } +} + +impl From for StandardLibraryError { + fn from(error: io::Error) -> Self { + StandardLibraryError::IoError(error) + } +} + +impl StandardLibrary { + pub fn from_name(name: &str) -> Option { + macro_rules! names { + {$($name:expr => $path:expr,)+} => { + match name { + $( + $name => { + let mut std = serde_yaml::from_str::( + include_str!($path) + ).unwrap_or_else(|error| { + panic!( + "default standard library '{}' failed deserialization: {}", + name, + error, + ) + }); + + if let Some(base_name) = &std.base { + let base = StandardLibrary::from_name(base_name); + + std.extend( + base.expect("built-in library based off of non-existent built-in"), + ); + } + + Some(std) + }, + )+ + + _ => None + } + }; + } + + names! { + "lua51" => "../../default_std/lua51.yml", + "lua52" => "../../default_std/lua52.yml", + "lua53" => "../../default_std/lua53.yml", + } + } + + /// Find a global in the standard library through its name path. + /// Handles all of the following cases: + /// 1. "x.y" where `x.y` is explicitly defined + /// 2. "x.y" where `x.*` is defined + /// 3. "x.y" where `x` is a struct with a `y` or `*` field + /// 4. "x.y.z" where `x.*.z` or `x.*.*` is defined + /// 5. "x.y.z" where `x.y` or `x.*` is defined as "any" + /// 6. "x.y" resolving to a read only property if only "x.y.z" (or x.y.*) is explicitly defined + pub fn find_global(&self, names: &[String]) -> Option<&Field> { + assert!(!names.is_empty()); + + if let Some(explicit_global) = self.globals.get(&names.join(".")) { + return Some(explicit_global); + } + + static READ_ONLY_FIELD: Field = Field { + field_kind: FieldKind::Property(PropertyWritability::ReadOnly), + }; + + #[derive(Clone, Debug)] + struct TreeNode<'a> { + field: &'a Field, + children: BTreeMap>, + } + + fn extract_into_tree<'a>( + names_to_fields: &'a BTreeMap, + ) -> BTreeMap> { + let mut fields: BTreeMap> = BTreeMap::new(); + + for (name, field) in names_to_fields { + let mut current = &mut fields; + + let mut split = name.split('.').collect::>(); + let final_name = split.pop().unwrap(); + + for segment in split { + current = &mut current + .entry(segment.to_string()) + .or_insert_with(|| TreeNode { + field: &READ_ONLY_FIELD, + children: BTreeMap::new(), + }) + .children; + } + + if let Some(existing_segment) = current.get_mut(final_name) { + existing_segment.field = field; + } else { + current.insert( + final_name.to_string(), + TreeNode { + field, + children: BTreeMap::new(), + }, + ); + } + } + + fields + } + + let global_fields = extract_into_tree(&self.globals); + let mut current = &global_fields; + + // TODO: This is really stupid lol + let mut last_extracted_struct; + + for name in names.iter().take(names.len() - 1) { + let found_segment = current.get(name).or_else(|| current.get("*"))?; + + match found_segment.field { + Field { + field_kind: FieldKind::Any, + } => { + return Some(found_segment.field); + } + + Field { + field_kind: FieldKind::Struct(struct_name), + } => { + let strukt = self + .structs + .get(struct_name) + .unwrap_or_else(|| panic!("struct `{struct_name}` not found")); + + last_extracted_struct = extract_into_tree(strukt); + current = &last_extracted_struct; + } + + _ => { + current = &found_segment.children; + } + } + } + + current + .get(names.last().unwrap()) + .or_else(|| current.get("*")) + .map(|node| node.field) + } + + pub fn get_globals_under<'a>(&'a self, name: &str) -> HashMap<&'a String, &'a Field> { + let mut globals = HashMap::new(); + + for (key, value) in self.globals.iter() { + if key.split_once('.').map_or(&**key, |x| x.0) == name { + globals.insert(key, value); + } + } + + globals + } + + pub fn extend(&mut self, other: StandardLibrary) { + self.structs.extend(other.structs); + + // let mut globals = other.globals.to_owned(); + let mut globals: BTreeMap = other + .globals + .into_iter() + .filter(|(other_field_name, other_field)| { + other_field.field_kind != FieldKind::Removed + && !matches!( + self.globals.get(other_field_name), + Some(Field { + field_kind: FieldKind::Removed, + }) + ) + }) + .collect(); + + globals.extend( + std::mem::take(&mut self.globals) + .into_iter() + .filter_map(|(key, value)| { + if value.field_kind == FieldKind::Removed { + None + } else { + Some((key, value)) + } + }), + ); + + self.globals = globals; + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct FunctionBehavior { + #[serde(rename = "args")] + pub arguments: Vec, + #[serde(default)] + #[serde(skip_serializing_if = "is_false")] + pub method: bool, +} + +fn is_false(value: &bool) -> bool { + !value +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Field { + #[serde(flatten)] + pub field_kind: FieldKind, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FieldKind { + Any, + Function(FunctionBehavior), + Property(PropertyWritability), + Struct(String), + Removed, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct TrueOnly; + +impl<'de> Deserialize<'de> for TrueOnly { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match bool::deserialize(deserializer) { + Ok(true) => Ok(TrueOnly), + _ => Err(de::Error::custom("expected `true`")), + } + } +} + +impl Serialize for TrueOnly { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bool(true) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(untagged)] +enum FieldKindSerde { + Any { any: TrueOnly }, + Function(FunctionBehavior), + Removed { removed: TrueOnly }, + Property { property: PropertyWritability }, + Struct { r#struct: String }, +} + +impl<'de> Deserialize<'de> for FieldKind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let field_kind = FieldKindSerde::deserialize(deserializer)?; + + Ok(match field_kind { + FieldKindSerde::Any { .. } => FieldKind::Any, + FieldKindSerde::Function(function_behavior) => FieldKind::Function(function_behavior), + FieldKindSerde::Removed { .. } => FieldKind::Removed, + FieldKindSerde::Property { property } => FieldKind::Property(property), + FieldKindSerde::Struct { r#struct } => FieldKind::Struct(r#struct), + }) + } +} + +impl Serialize for FieldKind { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let field_kind = match self { + FieldKind::Any => FieldKindSerde::Any { any: TrueOnly }, + FieldKind::Function(function_behavior) => { + FieldKindSerde::Function(function_behavior.to_owned()) + } + FieldKind::Removed => FieldKindSerde::Removed { removed: TrueOnly }, + FieldKind::Property(property_writability) => FieldKindSerde::Property { + property: *property_writability, + }, + + FieldKind::Struct(r#struct) => FieldKindSerde::Struct { + r#struct: r#struct.to_owned(), + }, + }; + + field_kind.serialize(serializer) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum PropertyWritability { + // New fields can't be added, and entire variable can't be overridden + ReadOnly, + // New fields can be added and set, but variable itself cannot be redefined + NewFields, + // New fields can't be added, but entire variable can be overridden + OverrideFields, + // New fields can be added and entire variable can be overridden + FullWrite, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Argument { + #[serde(default)] + #[serde(skip_serializing_if = "Required::required_no_message")] + pub required: Required, + #[serde(rename = "type")] + pub argument_type: ArgumentType, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +// TODO: Nilable types +pub enum ArgumentType { + Any, + Bool, + Constant(Vec), + Display(String), + // TODO: Optionally specify parameters + Function, + Nil, + Number, + String, + // TODO: Types for tables + Table, + // TODO: Support repeating types (like for string.char) + Vararg, +} + +impl Serialize for ArgumentType { + fn serialize(&self, serializer: S) -> Result { + match self { + &ArgumentType::Any + | &ArgumentType::Bool + | &ArgumentType::Function + | &ArgumentType::Nil + | &ArgumentType::Number + | &ArgumentType::String + | &ArgumentType::Table + | &ArgumentType::Vararg => serializer.serialize_str(&self.to_string()), + + ArgumentType::Constant(constants) => { + let mut seq = serializer.serialize_seq(Some(constants.len()))?; + for constant in constants { + seq.serialize_element(constant)?; + } + seq.end() + } + + ArgumentType::Display(display) => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("display", display)?; + map.end() + } + } + } +} + +impl<'de> Deserialize<'de> for ArgumentType { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(ArgumentTypeVisitor) + } +} + +struct ArgumentTypeVisitor; + +impl<'de> Visitor<'de> for ArgumentTypeVisitor { + type Value = ArgumentType; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an argument type or an array of constant strings") + } + + fn visit_map>(self, mut access: A) -> Result { + let mut map: HashMap = HashMap::new(); + + while let Some((key, value)) = access.next_entry()? { + map.insert(key, value); + } + + if let Some(display) = map.remove("display") { + Ok(ArgumentType::Display(display)) + } else { + Err(de::Error::custom( + "map value must have a `display` property", + )) + } + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut constants = Vec::new(); + + while let Some(value) = seq.next_element()? { + constants.push(value); + } + + Ok(ArgumentType::Constant(constants)) + } + + fn visit_str(self, value: &str) -> Result { + match value { + "any" => Ok(ArgumentType::Any), + "bool" => Ok(ArgumentType::Bool), + "function" => Ok(ArgumentType::Function), + "nil" => Ok(ArgumentType::Nil), + "number" => Ok(ArgumentType::Number), + "string" => Ok(ArgumentType::String), + "table" => Ok(ArgumentType::Table), + "..." => Ok(ArgumentType::Vararg), + other => Err(de::Error::custom(format!("unknown type {}", other))), + } + } +} + +impl fmt::Display for ArgumentType { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + ArgumentType::Any => write!(formatter, "any"), + ArgumentType::Bool => write!(formatter, "bool"), + ArgumentType::Constant(options) => write!( + formatter, + "{}", + // TODO: This gets pretty ugly with a lot of variants + options + .iter() + .map(|string| format!("\"{}\"", string)) + .collect::>() + .join(", ") + ), + ArgumentType::Display(display) => write!(formatter, "{}", display), + ArgumentType::Function => write!(formatter, "function"), + ArgumentType::Nil => write!(formatter, "nil"), + ArgumentType::Number => write!(formatter, "number"), + ArgumentType::String => write!(formatter, "string"), + ArgumentType::Table => write!(formatter, "table"), + ArgumentType::Vararg => write!(formatter, "..."), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Required { + NotRequired, + Required(Option), +} + +impl Required { + fn required_no_message(&self) -> bool { + self == &Required::Required(None) + } +} + +impl Default for Required { + fn default() -> Self { + Required::Required(None) + } +} + +impl<'de> Deserialize<'de> for Required { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(RequiredVisitor) + } +} + +impl Serialize for Required { + fn serialize(&self, serializer: S) -> Result { + match self { + Required::NotRequired => serializer.serialize_bool(false), + Required::Required(None) => serializer.serialize_bool(true), + Required::Required(Some(message)) => serializer.serialize_str(message), + } + } +} + +struct RequiredVisitor; + +impl<'de> Visitor<'de> for RequiredVisitor { + type Value = Required; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a boolean or a string message (when required)") + } + + fn visit_bool(self, value: bool) -> Result { + if value { + Ok(Required::Required(None)) + } else { + Ok(Required::NotRequired) + } + } + + fn visit_str(self, value: &str) -> Result { + Ok(Required::Required(Some(value.to_owned()))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn valid_serde() { + StandardLibrary::from_name("lua51").expect("lua51.toml wasn't found"); + StandardLibrary::from_name("lua52").expect("lua52.toml wasn't found"); + } +} diff --git a/selene-lib/src/standard_library.rs b/selene-lib/src/standard_library/v1.rs similarity index 58% rename from selene-lib/src/standard_library.rs rename to selene-lib/src/standard_library/v1.rs index 50a04cab..ddd6cb80 100644 --- a/selene-lib/src/standard_library.rs +++ b/selene-lib/src/standard_library/v1.rs @@ -1,7 +1,9 @@ +// An intentionally separate implementation of the standard library as it existed +// before version 2. +// This is so that any changes to the v2 standard library don't affect the old one. use std::{ collections::{BTreeMap, HashMap}, - fmt, fs, io, - path::Path, + fmt, }; use serde::{ @@ -38,249 +40,6 @@ pub struct StandardLibraryMeta { pub structs: Option>>, } -#[derive(Debug)] -pub enum StandardLibraryError { - DeserializeError(toml::de::Error), - IoError(io::Error), -} - -impl fmt::Display for StandardLibraryError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - match self { - StandardLibraryError::DeserializeError(error) => { - write!(formatter, "deserialize error: {}", error) - } - StandardLibraryError::IoError(error) => write!(formatter, "io error: {}", error), - } - } -} - -impl std::error::Error for StandardLibraryError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use StandardLibraryError::*; - - match self { - DeserializeError(error) => Some(error), - IoError(error) => Some(error), - } - } -} - -impl From for StandardLibraryError { - fn from(error: io::Error) -> Self { - StandardLibraryError::IoError(error) - } -} - -impl StandardLibrary { - pub fn from_name(name: &str) -> Option { - macro_rules! names { - {$($name:expr => $path:expr,)+} => { - match name { - $( - $name => { - let mut std = toml::from_str::( - include_str!($path) - ).unwrap_or_else(|error| { - panic!( - "default standard library '{}' failed deserialization: {}", - name, - error, - ) - }); - - if let Some(meta) = &std.meta { - if let Some(base_name) = &meta.base { - let base = StandardLibrary::from_name(base_name); - - std.extend( - base.expect("built-in library based off of non-existent built-in"), - ); - } - } - - std.inflate(); - - Some(std) - }, - )+ - - _ => None - } - }; - } - - names! { - "lua51" => "../default_std/lua51.toml", - "lua52" => "../default_std/lua52.toml", - } - } - - pub fn from_config_name( - name: &str, - directory: Option<&Path>, - ) -> Result, StandardLibraryError> { - let mut library: Option = None; - - for segment in name.split('+') { - let segment_library = match StandardLibrary::from_name(segment) { - Some(default) => default, - - None => { - let mut path = directory - .map(Path::to_path_buf) - .unwrap_or_else(|| - panic!( - "from_config_name used with no directory, but segment `{}` is not a built-in library", - segment - ) - ); - - path.push(format!("{}.toml", segment)); - match StandardLibrary::from_file(&path)? { - Some(library) => library, - None => return Ok(None), - } - } - }; - - match library { - Some(ref mut base) => base.extend(segment_library), - None => library = Some(segment_library), - }; - } - - if let Some(ref mut library) = library { - library.inflate(); - } - - Ok(library) - } - - pub fn from_file(filename: &Path) -> Result, StandardLibraryError> { - let content = fs::read_to_string(filename)?; - let mut library: StandardLibrary = - toml::from_str(&content).map_err(StandardLibraryError::DeserializeError)?; - - if let Some(meta) = &library.meta { - if let Some(base_name) = &meta.base { - if let Some(base) = StandardLibrary::from_config_name(base_name, filename.parent())? - { - library.extend(base); - } - } - } - - Ok(Some(library)) - } - - pub fn find_global(&self, names: &[String]) -> Option<&Field> { - assert!(!names.is_empty()); - let mut current = &self.globals; - - // Traverse through `foo.bar` in `foo.bar.baz` - for name in names.iter().take(names.len() - 1) { - if let Some(child) = current - .get(name) - .or_else(|| current.get("*")) - .map(|field| self.unstruct(field)) - { - match child { - Field::Any => { - current = &ANY_TABLE; - } - - Field::Complex { table, .. } if !table.is_empty() => { - current = table; - } - - _ => return None, - }; - } else { - return None; - } - } - - current - .get(names.last().unwrap()) - .or_else(|| current.get("*")) - .map(|field| self.unstruct(field)) - } - - pub fn unstruct<'a>(&'a self, field: &'a Field) -> &'a Field { - if let Field::Struct(name) = field { - self.structs - .get(name) - .unwrap_or_else(|| panic!("no struct named `{}` exists", name)) - } else { - field - } - } - - pub fn extend(&mut self, mut other: StandardLibrary) { - fn merge(into: &mut BTreeMap, other: &mut BTreeMap) { - for (k, v) in other { - let (k, mut v) = (k.to_owned(), v.to_owned()); - - if let Field::Removed = v { - into.remove(&k); - continue; - } - - if_chain::if_chain! { - if let Some(conflict) = into.get_mut(&k); - if let Field::Complex { - table: ref mut from_children, - .. - } = v; - if !from_children.is_empty(); - if let Field::Complex { table: into_children, .. } = conflict; - then { - merge(into_children, from_children); - continue; - } - } - - into.insert(k, v); - } - } - - if let Some(other_meta) = &mut other.meta { - if let Some(other_structs) = &mut other_meta.structs { - if self.meta.is_none() { - self.meta = Some(StandardLibraryMeta::default()); - } - - let meta = self.meta.as_mut().unwrap(); - - if let Some(structs) = meta.structs.as_mut() { - structs.extend(other_structs.iter().map(|(k, v)| (k.clone(), v.clone()))); - } else { - meta.structs = Some(other_structs.clone()); - } - } - } - - let mut globals = BTreeMap::new(); - merge(&mut globals, &mut other.globals); - merge(&mut globals, &mut self.globals); - self.globals = globals; - } - - pub fn inflate(&mut self) { - let structs = self - .meta - .as_ref() - .and_then(|meta| meta.structs.as_ref()) - .cloned(); - - for (name, children) in structs.unwrap_or_default() { - self.structs - .insert(name.to_owned(), children.clone().into()); - } - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct FunctionBehavior { pub arguments: Vec, @@ -465,20 +224,16 @@ pub struct Argument { } #[derive(Clone, Debug, PartialEq, Eq)] -// TODO: Nilable types pub enum ArgumentType { Any, Bool, Constant(Vec), Display(String), - // TODO: Optionally specify parameters Function, Nil, Number, String, - // TODO: Types for tables Table, - // TODO: Support repeating types (like for string.char) Vararg, } @@ -648,14 +403,3 @@ impl<'de> Visitor<'de> for RequiredVisitor { Ok(Required::Required(Some(value.to_owned()))) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn valid_serde() { - StandardLibrary::from_name("lua51").expect("lua51.toml wasn't found"); - StandardLibrary::from_name("lua52").expect("lua52.toml wasn't found"); - } -} diff --git a/selene-lib/src/standard_library/v1_upgrade.rs b/selene-lib/src/standard_library/v1_upgrade.rs new file mode 100644 index 00000000..fd152ce2 --- /dev/null +++ b/selene-lib/src/standard_library/v1_upgrade.rs @@ -0,0 +1,147 @@ +use std::collections::BTreeMap; + +use super::{v1, *}; + +impl From for StandardLibrary { + fn from(mut v1: v1::StandardLibrary) -> Self { + let mut standard_library = StandardLibrary::default(); + + if let Some(meta) = v1.meta.take() { + standard_library.base = meta.base; + standard_library.name = meta.name; + + if let Some(v1_structs) = meta.structs { + for (name, children) in v1_structs { + let mut struct_fields = BTreeMap::new(); + + for (name, v1_struct_field) in children { + struct_fields.extend(unpack_v1_field(name, v1_struct_field)); + } + + standard_library.structs.insert(name, struct_fields); + } + } + } + + let mut globals = BTreeMap::new(); + + for (name, v1_global) in v1.globals { + globals.extend(unpack_v1_field(name, v1_global)); + } + + standard_library.globals = globals; + + standard_library + } +} + +impl From for Argument { + fn from(v1_argument: v1::Argument) -> Self { + Argument { + required: v1_argument.required.into(), + argument_type: v1_argument.argument_type.into(), + } + } +} + +impl From for ArgumentType { + fn from(v1_argument_type: v1::ArgumentType) -> Self { + match v1_argument_type { + v1::ArgumentType::Any => ArgumentType::Any, + v1::ArgumentType::Bool => ArgumentType::Bool, + v1::ArgumentType::Constant(constants) => ArgumentType::Constant(constants), + v1::ArgumentType::Display(message) => ArgumentType::Display(message), + v1::ArgumentType::Function => ArgumentType::Function, + v1::ArgumentType::Nil => ArgumentType::Nil, + v1::ArgumentType::Number => ArgumentType::Number, + v1::ArgumentType::String => ArgumentType::String, + v1::ArgumentType::Table => ArgumentType::Table, + v1::ArgumentType::Vararg => ArgumentType::Vararg, + } + } +} + +fn unpack_v1_field(name: String, v1_field: v1::Field) -> BTreeMap { + let mut upgraded_fields = BTreeMap::new(); + let mut v1_fields = vec![(name, v1_field)]; + + while let Some((name, field)) = v1_fields.pop() { + match field { + v1::Field::Any => { + upgraded_fields.insert( + name, + Field { + field_kind: FieldKind::Any, + }, + ); + } + + v1::Field::Complex { function, table } => { + for (child_name, child_field) in table { + v1_fields.push((format!("{name}.{child_name}").to_owned(), child_field)); + } + + if let Some(function) = function { + upgraded_fields.insert( + name, + Field { + field_kind: FieldKind::Function(FunctionBehavior { + arguments: function.arguments.into_iter().map(Into::into).collect(), + method: function.method, + }), + }, + ); + } + } + + v1::Field::Property { writable } => { + upgraded_fields.insert( + name, + Field { + field_kind: FieldKind::Property(writable.into()), + }, + ); + } + + v1::Field::Struct(struct_name) => { + upgraded_fields.insert( + name, + Field { + field_kind: FieldKind::Struct(struct_name), + }, + ); + } + + v1::Field::Removed => { + upgraded_fields.insert( + name, + Field { + field_kind: FieldKind::Removed, + }, + ); + } + } + } + + upgraded_fields +} + +impl From> for PropertyWritability { + fn from(v1_writable: Option) -> Self { + match v1_writable { + Some(v1::Writable::Full) => PropertyWritability::FullWrite, + Some(v1::Writable::NewFields) => PropertyWritability::NewFields, + Some(v1::Writable::Overridden) => PropertyWritability::OverrideFields, + None => PropertyWritability::ReadOnly, + } + } +} + +impl From for Required { + fn from(v1_required: v1::Required) -> Self { + match v1_required { + v1::Required::NotRequired => Required::NotRequired, + v1::Required::Required(message) => Required::Required(message), + } + } +} diff --git a/selene-lib/tests/lints/standard_library/wildcard.lua b/selene-lib/tests/lints/standard_library/wildcard.lua index 177ea748..e7763576 100644 --- a/selene-lib/tests/lints/standard_library/wildcard.lua +++ b/selene-lib/tests/lints/standard_library/wildcard.lua @@ -7,3 +7,6 @@ script.Child = "Oops" script.Child.Name = "Okay" script.Child.Grandchild.Name = "Okay" + +print(x.y.z) +print(x.y.fail) diff --git a/selene-lib/tests/lints/standard_library/wildcard.std.toml b/selene-lib/tests/lints/standard_library/wildcard.std.toml index 75598157..b08bdbc0 100644 --- a/selene-lib/tests/lints/standard_library/wildcard.std.toml +++ b/selene-lib/tests/lints/standard_library/wildcard.std.toml @@ -11,3 +11,6 @@ writable = "overridden" [script."*"."*"] property = true writable = "full" + +[x."*".z] +property = true diff --git a/selene-lib/tests/lints/standard_library/wildcard.stderr b/selene-lib/tests/lints/standard_library/wildcard.stderr index 487104a3..e15da4d1 100644 --- a/selene-lib/tests/lints/standard_library/wildcard.stderr +++ b/selene-lib/tests/lints/standard_library/wildcard.stderr @@ -16,3 +16,9 @@ error[incorrect_standard_library_use]: standard library global `script.Child` is 6 │ script.Child = "Oops" │ ^^^^^^^^^^^^ +error[incorrect_standard_library_use]: standard library global `x.y` does not contain the field `fail` + ┌─ wildcard.lua:12:7 + │ +12 │ print(x.y.fail) + │ ^^^^^^^^ + diff --git a/selene/Cargo.toml b/selene/Cargo.toml index 7df8404e..994c1b2f 100644 --- a/selene/Cargo.toml +++ b/selene/Cargo.toml @@ -8,7 +8,7 @@ categories = ["command-line-utilities"] keywords = ["lua", "linter", "lua51"] repository = "https://github.com/Kampfkarren/selene" documentation = "https://kampfkarren.github.io/selene/" -edition = "2018" +edition = "2021" [dependencies] atty = "0.2" @@ -16,19 +16,22 @@ cfg-if = "0.1" chrono = "0.4" codespan = { version = "0.9", features = ["serialization"] } codespan-reporting = { version = "0.9", features = ["serialization"] } +color-eyre = "0.6.1" +dirs = "4.0.0" full_moon = "0.15.0" -lazy_static = "1.4" glob = "0.3" +lazy_static = "1.4" num_cpus = "1.10" -reqwest = { version = "0.9", optional = true } selene-lib = { path = "../selene-lib", version = "=0.17.0", default-features = false } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_yaml = "0.8.24" structopt = "0.3" termcolor = "1.0" threadpool = "1.7" toml = "0.5" +ureq = { version = "2.4.0", features = ["json"], optional = true } [features] default = ["roblox"] -roblox = ["selene-lib/roblox", "full_moon/roblox", "reqwest"] +roblox = ["selene-lib/roblox", "full_moon/roblox", "ureq"] diff --git a/selene/src/main.rs b/selene/src/main.rs index 8c737759..6e80d4e3 100644 --- a/selene/src/main.rs +++ b/selene/src/main.rs @@ -1,663 +1,667 @@ -use std::{ - ffi::OsString, - fmt, fs, - io::{self, Read, Write}, - path::{Path, PathBuf}, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, RwLock, - }, -}; - -use codespan_reporting::{ - diagnostic::{ - Diagnostic as CodespanDiagnostic, Label as CodespanLabel, Severity as CodespanSeverity, - }, - term::DisplayStyle as CodespanDisplayStyle, -}; -use selene_lib::{rules::Severity, standard_library::StandardLibrary, *}; -use structopt::{clap, StructOpt}; -use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use threadpool::ThreadPool; - -mod json_output; -mod opts; -#[cfg(feature = "roblox")] -mod roblox; - -macro_rules! error { - ($fmt:expr) => { - error(fmt::format(format_args!($fmt))).unwrap() - }; - - ($fmt:expr, $($args:tt)*) => { - error(fmt::format(format_args!($fmt, $($args)*))).unwrap() - }; -} - -lazy_static::lazy_static! { - static ref OPTIONS: RwLock> = RwLock::new(None); -} - -static LINT_ERRORS: AtomicUsize = AtomicUsize::new(0); -static LINT_WARNINGS: AtomicUsize = AtomicUsize::new(0); -static PARSE_ERRORS: AtomicUsize = AtomicUsize::new(0); - -fn get_color() -> ColorChoice { - let lock = OPTIONS.read().unwrap(); - let opts = lock.as_ref().unwrap(); - - match opts.color { - opts::Color::Always => ColorChoice::Always, - opts::Color::Auto => { - if atty::is(atty::Stream::Stdout) { - ColorChoice::Auto - } else { - ColorChoice::Never - } - } - opts::Color::Never => ColorChoice::Never, - } -} - -fn error(text: String) -> io::Result<()> { - let mut stderr = StandardStream::stderr(get_color()); - stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?; - write!(&mut stderr, "ERROR: ")?; - stderr.reset()?; - writeln!(&mut stderr, "{}", text)?; - Ok(()) -} - -fn log_total(parse_errors: usize, lint_errors: usize, lint_warnings: usize) -> io::Result<()> { - let mut stdout = StandardStream::stdout(get_color()); - - stdout.reset()?; - writeln!(&mut stdout, "Results:")?; - - let mut stat = |number: usize, label: &str| -> io::Result<()> { - if number > 0 { - stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?; - } else { - stdout.reset()?; - } - - write!(&mut stdout, "{}", number)?; - stdout.reset()?; - writeln!(&mut stdout, " {}", label) - }; - - stat(lint_errors, "errors")?; - stat(lint_warnings, "warnings")?; - stat(parse_errors, "parse errors")?; - - Ok(()) -} - -fn emit_codespan( - writer: &mut impl termcolor::WriteColor, - files: &codespan::Files<&str>, - diagnostic: &CodespanDiagnostic, -) { - let lock = OPTIONS.read().unwrap(); - let opts = lock.as_ref().unwrap(); - - let config = &codespan_reporting::term::Config { - display_style: if opts.quiet() { - CodespanDisplayStyle::Short - } else { - CodespanDisplayStyle::Rich - }, - ..Default::default() - }; - - if let Some(opts::DisplayStyle::Json) = opts.display_style { - writeln!( - writer, - "{}", - json_output::diagnostic_to_json(diagnostic, files).unwrap() - ) - .unwrap(); - } else { - codespan_reporting::term::emit(writer, config, files, diagnostic) - .expect("couldn't emit error to codespan"); - } -} - -fn emit_codespan_locked( - files: &codespan::Files<&str>, - diagnostic: &CodespanDiagnostic, -) { - let stdout = termcolor::StandardStream::stdout(get_color()); - let mut stdout = stdout.lock(); - - emit_codespan(&mut stdout, files, diagnostic); -} - -fn read(checker: &Checker, filename: &Path, mut reader: R) { - let mut buffer = Vec::new(); - if let Err(error) = reader.read_to_end(&mut buffer) { - error!( - "Couldn't read contents of file {}: {}", - filename.display(), - error, - ); - - LINT_ERRORS.fetch_add(1, Ordering::SeqCst); - return; - } - - let contents = String::from_utf8_lossy(&buffer); - - let lock = OPTIONS.read().unwrap(); - let opts = lock.as_ref().unwrap(); - - let mut files = codespan::Files::new(); - let source_id = files.add(filename.as_os_str(), &*contents); - - let ast = match full_moon::parse(&contents) { - Ok(ast) => ast, - Err(error) => { - PARSE_ERRORS.fetch_add(1, Ordering::SeqCst); - - match error { - full_moon::Error::AstError(full_moon::ast::AstError::UnexpectedToken { - token, - additional, - }) => emit_codespan_locked( - &files, - &CodespanDiagnostic { - severity: CodespanSeverity::Error, - code: Some("parse_error".to_owned()), - message: format!("unexpected token `{}`", token), - labels: vec![CodespanLabel::primary( - source_id, - codespan::Span::new( - token.start_position().bytes() as u32, - token.end_position().bytes() as u32, - ), - ) - .with_message(additional.unwrap_or_default())], - notes: Vec::new(), - }, - ), - full_moon::Error::TokenizerError(error) => emit_codespan_locked( - &files, - &CodespanDiagnostic { - severity: CodespanSeverity::Error, - code: Some("parse_error".to_owned()), - message: match error.error() { - full_moon::tokenizer::TokenizerErrorType::UnclosedComment => { - "unclosed comment".to_string() - } - full_moon::tokenizer::TokenizerErrorType::UnclosedString => { - "unclosed string".to_string() - } - full_moon::tokenizer::TokenizerErrorType::UnexpectedShebang => { - "unexpected shebang".to_string() - } - full_moon::tokenizer::TokenizerErrorType::UnexpectedToken( - character, - ) => { - format!("unexpected character {}", character) - } - full_moon::tokenizer::TokenizerErrorType::InvalidSymbol(symbol) => { - format!("invalid symbol {}", symbol) - } - }, - labels: vec![CodespanLabel::primary( - source_id, - codespan::Span::new( - error.position().bytes() as u32, - error.position().bytes() as u32, - ), - )], - notes: Vec::new(), - }, - ), - _ => error!("Error parsing {}: {}", filename.display(), error), - } - - return; - } - }; - - let mut diagnostics = checker.test_on(&ast); - diagnostics.sort_by_key(|diagnostic| diagnostic.diagnostic.start_position()); - - let (mut errors, mut warnings) = (0, 0); - for diagnostic in &diagnostics { - match diagnostic.severity { - Severity::Allow => {} - Severity::Error => errors += 1, - Severity::Warning => warnings += 1, - }; - } - - LINT_ERRORS.fetch_add(errors, Ordering::SeqCst); - LINT_WARNINGS.fetch_add(warnings, Ordering::SeqCst); - - let stdout = termcolor::StandardStream::stdout(get_color()); - let mut stdout = stdout.lock(); - - for diagnostic in diagnostics { - if opts.luacheck { - // Existing Luacheck consumers presumably use --formatter plain - let primary_label = &diagnostic.diagnostic.primary_label; - let end = files.location(source_id, primary_label.range.1).unwrap(); - - // Closures in Rust cannot call themselves recursively, especially not mutable ones. - // Luacheck only allows one line ranges, so we just repeat the lint for every line it spans. - // This would be frustrating for a human to read, but consumers (editors) will instead show it - // as a native implementation would. - let mut stack = Vec::new(); - - let mut write = |stack: &mut Vec<_>, start: codespan::Location| -> io::Result<()> { - write!(stdout, "{}:", filename.display())?; - write!(stdout, "{}:{}", start.line.number(), start.column.number())?; - - if opts.ranges { - write!( - stdout, - "-{}", - if start.line != end.line { - // Report to the end of the line - files - .source(source_id) - .lines() - .nth(start.line.to_usize()) - .unwrap() - .chars() - .count() - } else { - end.column.to_usize() - } - )?; - } - - // The next line will be displayed just like this one - if start.line != end.line { - stack.push(codespan::Location::new( - (start.line.to_usize() + 1) as u32, - 0, - )); - } - - write!( - stdout, - ": ({}000) ", - match diagnostic.severity { - Severity::Allow => return Ok(()), - Severity::Error => "E", - Severity::Warning => "W", - } - )?; - - write!(stdout, "[{}] ", diagnostic.diagnostic.code)?; - write!(stdout, "{}", diagnostic.diagnostic.message)?; - - if !diagnostic.diagnostic.notes.is_empty() { - write!(stdout, "\n{}", diagnostic.diagnostic.notes.join("\n"))?; - } - - writeln!(stdout)?; - Ok(()) - }; - - write( - &mut stack, - files.location(source_id, primary_label.range.0).unwrap(), - ) - .unwrap(); - - while let Some(new_start) = stack.pop() { - write(&mut stack, new_start).unwrap(); - } - } else { - let diagnostic = diagnostic.diagnostic.into_codespan_diagnostic( - source_id, - match diagnostic.severity { - Severity::Allow => continue, - Severity::Error => CodespanSeverity::Error, - Severity::Warning => CodespanSeverity::Warning, - }, - ); - - emit_codespan(&mut stdout, &files, &diagnostic); - } - } -} - -fn read_file(checker: &Checker, filename: &Path) { - read( - checker, - filename, - match fs::File::open(filename) { - Ok(file) => file, - Err(error) => { - error!("Couldn't open file {}: {}", filename.display(), error); - LINT_ERRORS.fetch_add(1, Ordering::SeqCst); - return; - } - }, - ); -} - -fn start(matches: opts::Options) { - #[cfg(feature = "roblox")] - { - if let Some(opts::Command::GenerateRobloxStd { deprecated }) = matches.command { - println!("Generating Roblox standard library..."); - - if let Err(error) = generate_roblox_std(deprecated) { - error!("Couldn't create roblox standard library: {}", error); - std::process::exit(1); - } - - return; - } - } - - *OPTIONS.write().unwrap() = Some(matches.clone()); - - let config: CheckerConfig = match matches.config { - Some(config_file) => { - let config_contents = match fs::read_to_string(config_file) { - Ok(contents) => contents, - Err(error) => { - error!("Couldn't read config file: {}", error); - std::process::exit(1); - } - }; - - match toml::from_str(&config_contents) { - Ok(config) => config, - Err(error) => { - error!("Config file not in correct format: {}", error); - std::process::exit(1); - } - } - } - - None => match fs::read_to_string("selene.toml") { - Ok(config_contents) => match toml::from_str(&config_contents) { - Ok(config) => config, - Err(error) => { - error!("Config file not in correct format: {}", error); - std::process::exit(1); - } - }, - - Err(_) => CheckerConfig::default(), - }, - }; - - let current_dir = std::env::current_dir().unwrap(); - - let standard_library = match StandardLibrary::from_config_name(&config.std, Some(¤t_dir)) - { - Ok(Some(library)) => library, - - Ok(None) => { - error!("Standard library was empty."); - std::process::exit(1); - } - - Err(_) => { - if cfg!(feature = "roblox") - && config.std.split('+').any(|name| name == "roblox") - && !Path::new("roblox.toml").exists() - { - eprint!("Roblox standard library could not be found in this directory. "); - eprintln!("We are automatically generating one for you now!"); - - eprint!("By the way, you can do this manually in the future if you need "); - eprint!("to use new Roblox features with: "); - eprintln!("`selene generate-roblox-std`."); - - if let Err(error) = generate_roblox_std(false) { - error!("Could not create roblox standard library: {}", error); - std::process::exit(1); - } - } - - let missing_files: Vec = config - .std - .split('+') - .map(|name| format!("{}.toml", name)) - .map(|name| PathBuf::from(&name)) - .filter(|path| !path.exists()) - .collect(); - - if !missing_files.is_empty() { - eprintln!( - "`std = \"{}\"`, but some files could not be found:", - config.std - ); - - for path in missing_files { - eprintln!(" `{}`", path.display()); - } - - error!("Could not find all standard library files"); - std::process::exit(1); - } - - match StandardLibrary::from_config_name(&config.std, Some(¤t_dir)) { - Ok(Some(library)) => library, - - // This is technically reachable if you edit your config while it is generating. - Ok(None) => { - error!("Standard library was empty, did you edit your config while running selene?"); - std::process::exit(1); - } - - Err(error) => { - error!("Could not retrieve standard library: {}", error); - std::process::exit(1); - } - } - } - }; - - let checker = Arc::new(match Checker::new(config, standard_library) { - Ok(checker) => checker, - Err(error) => { - error!("{}", error); - std::process::exit(1); - } - }); - - let pool = ThreadPool::new(matches.num_threads); - - for filename in &matches.files { - if filename == "-" { - let checker = Arc::clone(&checker); - pool.execute(move || read(&checker, Path::new("-"), io::stdin().lock())); - continue; - } - - match fs::metadata(filename) { - Ok(metadata) => { - if metadata.is_file() { - let checker = Arc::clone(&checker); - let filename = filename.to_owned(); - - pool.execute(move || read_file(&checker, Path::new(&filename))); - } else if metadata.is_dir() { - let glob = match glob::glob(&format!( - "{}/{}", - filename.to_string_lossy(), - matches.pattern, - )) { - Ok(glob) => glob, - Err(error) => { - error!("Invalid glob pattern: {}", error); - return; - } - }; - - for entry in glob { - match entry { - Ok(path) => { - let checker = Arc::clone(&checker); - - pool.execute(move || read_file(&checker, &path)); - } - - Err(error) => { - error!( - "Couldn't open file {}: {}", - filename.to_string_lossy(), - error - ); - } - }; - } - } else { - unreachable!("Somehow got a symlink from the files?"); - } - } - - Err(error) => { - error!( - "Error getting metadata of {}: {}", - filename.to_string_lossy(), - error - ); - - LINT_ERRORS.fetch_add(1, Ordering::SeqCst); - } - }; - } - - pool.join(); - - let (parse_errors, lint_errors, lint_warnings) = ( - PARSE_ERRORS.load(Ordering::SeqCst), - LINT_ERRORS.load(Ordering::SeqCst), - LINT_WARNINGS.load(Ordering::SeqCst), - ); - - if !matches.luacheck && !matches.no_summary { - log_total(parse_errors, lint_errors, lint_warnings).ok(); - } - - if parse_errors + lint_errors + lint_warnings + pool.panic_count() > 0 { - std::process::exit(1); - } -} - -fn main() { - let mut luacheck = false; - - if let Ok(path) = std::env::current_exe() { - if let Some(stem) = path.file_stem() { - if stem.to_str() == Some("luacheck") { - luacheck = true; - } - } - } - - start(get_opts(luacheck)); -} - -// Will attempt to get the options. -// Different from Options::from_args() as if in Luacheck mode -// (either found from --luacheck or from the LUACHECK AtomicBool) -// it will ignore all extra parameters. -// If not in luacheck mode and errors are found, will exit. -fn get_opts(luacheck: bool) -> opts::Options { - get_opts_safe(std::env::args_os().collect::>(), luacheck) - .unwrap_or_else(|err| err.exit()) -} - -fn get_opts_safe(mut args: Vec, luacheck: bool) -> Result { - let mut first_error: Option = None; - - loop { - match opts::Options::from_iter_safe(&args) { - Ok(mut options) => match first_error { - Some(error) => { - if options.luacheck || luacheck { - options.luacheck = true; - break Ok(options); - } else { - break Err(error); - } - } - - None => break Ok(options), - }, - - Err(err) => match err.kind { - clap::ErrorKind::UnknownArgument => { - let bad_arg = - &err.info.as_ref().expect("no info for UnknownArgument")[0].to_owned(); - - args = args - .drain(..) - .filter(|arg| arg.to_string_lossy().split('=').next().unwrap() != bad_arg) - .collect(); - - if first_error.is_none() { - first_error = Some(err); - } - } - - _ => break Err(err), - }, - } - } -} - -#[cfg(feature = "roblox")] -fn generate_roblox_std(show_deprecated: bool) -> Result { - let (contents, std) = roblox::RobloxGenerator { - std: roblox::RobloxGenerator::base_std()?, - show_deprecated, - } - .generate()?; - - fs::File::create("roblox.toml") - .and_then(|mut file| file.write_all(&contents)) - .map_err(roblox::GenerateError::Io)?; - - Ok(std) -} - -#[cfg(not(feature = "roblox"))] -fn generate_roblox_std(_: bool) -> Result { - unreachable!("generate_roblox_std called when Roblox feature was not installed!"); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn args(mut args: Vec<&str>) -> Vec { - args.insert(0, "selene"); - args.into_iter().map(OsString::from).collect() - } - - #[test] - fn test_luacheck_opts() { - assert!(get_opts_safe(args(vec!["file"]), false).is_ok()); - assert!(get_opts_safe(args(vec!["--fail", "files"]), false).is_err()); - - match get_opts_safe(args(vec!["--luacheck", "--fail", "files"]), false) { - Ok(opts) => { - assert!(opts.luacheck); - assert_eq!(opts.files, vec![OsString::from("files")]); - } - - Err(err) => { - panic!("selene --luacheck --fail files returned Err: {:?}", err); - } - } - - assert!(get_opts_safe(args(vec!["-", "--formatter=plain"]), true).is_ok()); - - assert!(get_opts_safe(args(vec!["--fail", "files"]), true).is_ok()); - } -} +use std::{ + ffi::OsString, + fmt, fs, + io::{self, Read, Write}, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, RwLock, + }, +}; + +use codespan_reporting::{ + diagnostic::{ + Diagnostic as CodespanDiagnostic, Label as CodespanLabel, Severity as CodespanSeverity, + }, + term::DisplayStyle as CodespanDisplayStyle, +}; +use selene_lib::{rules::Severity, standard_library::StandardLibrary, *}; +use structopt::{clap, StructOpt}; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; +use threadpool::ThreadPool; +use upgrade_std::upgrade_std; + +mod json_output; +mod opts; +#[cfg(feature = "roblox")] +mod roblox; +mod standard_library; +mod upgrade_std; + +macro_rules! error { + ($fmt:expr) => { + error(&fmt::format(format_args!($fmt))) + }; + + ($fmt:expr, $($args:tt)*) => { + error(&fmt::format(format_args!($fmt, $($args)*))) + }; +} + +lazy_static::lazy_static! { + static ref OPTIONS: RwLock> = RwLock::new(None); +} + +static LINT_ERRORS: AtomicUsize = AtomicUsize::new(0); +static LINT_WARNINGS: AtomicUsize = AtomicUsize::new(0); +static PARSE_ERRORS: AtomicUsize = AtomicUsize::new(0); + +fn get_color() -> ColorChoice { + let lock = OPTIONS.read().unwrap(); + let opts = lock.as_ref().unwrap(); + + match opts.color { + opts::Color::Always => ColorChoice::Always, + opts::Color::Auto => { + if atty::is(atty::Stream::Stdout) { + ColorChoice::Auto + } else { + ColorChoice::Never + } + } + opts::Color::Never => ColorChoice::Never, + } +} + +pub fn error(text: &str) { + let mut stderr = StandardStream::stderr(get_color()); + stderr + .set_color(ColorSpec::new().set_fg(Some(Color::Red))) + .unwrap(); + write!(&mut stderr, "ERROR: ").unwrap(); + stderr.reset().unwrap(); + writeln!(&mut stderr, "{}", text).unwrap(); +} + +fn log_total(parse_errors: usize, lint_errors: usize, lint_warnings: usize) -> io::Result<()> { + let mut stdout = StandardStream::stdout(get_color()); + + stdout.reset()?; + writeln!(&mut stdout, "Results:")?; + + let mut stat = |number: usize, label: &str| -> io::Result<()> { + if number > 0 { + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?; + } else { + stdout.reset()?; + } + + write!(&mut stdout, "{}", number)?; + stdout.reset()?; + writeln!(&mut stdout, " {}", label) + }; + + stat(lint_errors, "errors")?; + stat(lint_warnings, "warnings")?; + stat(parse_errors, "parse errors")?; + + Ok(()) +} + +fn emit_codespan( + writer: &mut impl termcolor::WriteColor, + files: &codespan::Files<&str>, + diagnostic: &CodespanDiagnostic, +) { + let lock = OPTIONS.read().unwrap(); + let opts = lock.as_ref().unwrap(); + + let config = &codespan_reporting::term::Config { + display_style: if opts.quiet() { + CodespanDisplayStyle::Short + } else { + CodespanDisplayStyle::Rich + }, + ..Default::default() + }; + + if let Some(opts::DisplayStyle::Json) = opts.display_style { + writeln!( + writer, + "{}", + json_output::diagnostic_to_json(diagnostic, files).unwrap() + ) + .unwrap(); + } else { + codespan_reporting::term::emit(writer, config, files, diagnostic) + .expect("couldn't emit error to codespan"); + } +} + +fn emit_codespan_locked( + files: &codespan::Files<&str>, + diagnostic: &CodespanDiagnostic, +) { + let stdout = termcolor::StandardStream::stdout(get_color()); + let mut stdout = stdout.lock(); + + emit_codespan(&mut stdout, files, diagnostic); +} + +fn read(checker: &Checker, filename: &Path, mut reader: R) { + let mut buffer = Vec::new(); + if let Err(error) = reader.read_to_end(&mut buffer) { + error!( + "Couldn't read contents of file {}: {}", + filename.display(), + error, + ); + + LINT_ERRORS.fetch_add(1, Ordering::SeqCst); + return; + } + + let contents = String::from_utf8_lossy(&buffer); + + let lock = OPTIONS.read().unwrap(); + let opts = lock.as_ref().unwrap(); + + let mut files = codespan::Files::new(); + let source_id = files.add(filename.as_os_str(), &*contents); + + let ast = match full_moon::parse(&contents) { + Ok(ast) => ast, + Err(error) => { + PARSE_ERRORS.fetch_add(1, Ordering::SeqCst); + + match error { + full_moon::Error::AstError(full_moon::ast::AstError::UnexpectedToken { + token, + additional, + }) => emit_codespan_locked( + &files, + &CodespanDiagnostic { + severity: CodespanSeverity::Error, + code: Some("parse_error".to_owned()), + message: format!("unexpected token `{}`", token), + labels: vec![CodespanLabel::primary( + source_id, + codespan::Span::new( + token.start_position().bytes() as u32, + token.end_position().bytes() as u32, + ), + ) + .with_message(additional.unwrap_or_default())], + notes: Vec::new(), + }, + ), + full_moon::Error::TokenizerError(error) => emit_codespan_locked( + &files, + &CodespanDiagnostic { + severity: CodespanSeverity::Error, + code: Some("parse_error".to_owned()), + message: match error.error() { + full_moon::tokenizer::TokenizerErrorType::UnclosedComment => { + "unclosed comment".to_string() + } + full_moon::tokenizer::TokenizerErrorType::UnclosedString => { + "unclosed string".to_string() + } + full_moon::tokenizer::TokenizerErrorType::UnexpectedShebang => { + "unexpected shebang".to_string() + } + full_moon::tokenizer::TokenizerErrorType::UnexpectedToken( + character, + ) => { + format!("unexpected character {}", character) + } + full_moon::tokenizer::TokenizerErrorType::InvalidSymbol(symbol) => { + format!("invalid symbol {}", symbol) + } + }, + labels: vec![CodespanLabel::primary( + source_id, + codespan::Span::new( + error.position().bytes() as u32, + error.position().bytes() as u32, + ), + )], + notes: Vec::new(), + }, + ), + _ => error!("Error parsing {}: {}", filename.display(), error), + } + + return; + } + }; + + let mut diagnostics = checker.test_on(&ast); + diagnostics.sort_by_key(|diagnostic| diagnostic.diagnostic.start_position()); + + let (mut errors, mut warnings) = (0, 0); + for diagnostic in &diagnostics { + match diagnostic.severity { + Severity::Allow => {} + Severity::Error => errors += 1, + Severity::Warning => warnings += 1, + }; + } + + LINT_ERRORS.fetch_add(errors, Ordering::SeqCst); + LINT_WARNINGS.fetch_add(warnings, Ordering::SeqCst); + + let stdout = termcolor::StandardStream::stdout(get_color()); + let mut stdout = stdout.lock(); + + for diagnostic in diagnostics { + if opts.luacheck { + // Existing Luacheck consumers presumably use --formatter plain + let primary_label = &diagnostic.diagnostic.primary_label; + let end = files.location(source_id, primary_label.range.1).unwrap(); + + // Closures in Rust cannot call themselves recursively, especially not mutable ones. + // Luacheck only allows one line ranges, so we just repeat the lint for every line it spans. + // This would be frustrating for a human to read, but consumers (editors) will instead show it + // as a native implementation would. + let mut stack = Vec::new(); + + let mut write = |stack: &mut Vec<_>, start: codespan::Location| -> io::Result<()> { + write!(stdout, "{}:", filename.display())?; + write!(stdout, "{}:{}", start.line.number(), start.column.number())?; + + if opts.ranges { + write!( + stdout, + "-{}", + if start.line != end.line { + // Report to the end of the line + files + .source(source_id) + .lines() + .nth(start.line.to_usize()) + .unwrap() + .chars() + .count() + } else { + end.column.to_usize() + } + )?; + } + + // The next line will be displayed just like this one + if start.line != end.line { + stack.push(codespan::Location::new( + (start.line.to_usize() + 1) as u32, + 0, + )); + } + + write!( + stdout, + ": ({}000) ", + match diagnostic.severity { + Severity::Allow => return Ok(()), + Severity::Error => "E", + Severity::Warning => "W", + } + )?; + + write!(stdout, "[{}] ", diagnostic.diagnostic.code)?; + write!(stdout, "{}", diagnostic.diagnostic.message)?; + + if !diagnostic.diagnostic.notes.is_empty() { + write!(stdout, "\n{}", diagnostic.diagnostic.notes.join("\n"))?; + } + + writeln!(stdout)?; + Ok(()) + }; + + write( + &mut stack, + files.location(source_id, primary_label.range.0).unwrap(), + ) + .unwrap(); + + while let Some(new_start) = stack.pop() { + write(&mut stack, new_start).unwrap(); + } + } else { + let diagnostic = diagnostic.diagnostic.into_codespan_diagnostic( + source_id, + match diagnostic.severity { + Severity::Allow => continue, + Severity::Error => CodespanSeverity::Error, + Severity::Warning => CodespanSeverity::Warning, + }, + ); + + emit_codespan(&mut stdout, &files, &diagnostic); + } + } +} + +fn read_file(checker: &Checker, filename: &Path) { + read( + checker, + filename, + match fs::File::open(filename) { + Ok(file) => file, + Err(error) => { + error!("Couldn't open file {}: {}", filename.display(), error); + LINT_ERRORS.fetch_add(1, Ordering::SeqCst); + return; + } + }, + ); +} + +fn start(matches: opts::Options) { + match matches.command { + #[cfg(feature = "roblox")] + Some(opts::Command::GenerateRobloxStd { deprecated }) => { + println!("Generating Roblox standard library..."); + + if let Err(error) = generate_roblox_std(deprecated) { + error!("Couldn't create roblox standard library: {}", error); + std::process::exit(1); + } + + return; + } + + #[cfg(feature = "roblox")] + Some(opts::Command::UpdateRobloxStd) => { + println!("Updating Roblox standard library..."); + + if let Err(error) = roblox::update_roblox_std() { + error!("Couldn't update Roblox standard library: {error}"); + std::process::exit(1); + } + + return; + } + + Some(opts::Command::UpgradeStd { filename }) => { + if let Err(error) = upgrade_std(filename) { + error!("Couldn't upgrade standard library: {error}"); + std::process::exit(1); + } + + return; + } + + None => {} + } + + *OPTIONS.write().unwrap() = Some(matches.clone()); + + let config: CheckerConfig = match matches.config { + Some(config_file) => { + let config_contents = match fs::read_to_string(config_file) { + Ok(contents) => contents, + Err(error) => { + error!("Couldn't read config file: {}", error); + std::process::exit(1); + } + }; + + match toml::from_str(&config_contents) { + Ok(config) => config, + Err(error) => { + error!("Config file not in correct format: {}", error); + std::process::exit(1); + } + } + } + + None => match fs::read_to_string("selene.toml") { + Ok(config_contents) => match toml::from_str(&config_contents) { + Ok(config) => config, + Err(error) => { + error!("Config file not in correct format: {}", error); + std::process::exit(1); + } + }, + + Err(_) => CheckerConfig::default(), + }, + }; + + let current_dir = std::env::current_dir().unwrap(); + + let standard_library = + match standard_library::collect_standard_library(&config, &config.std, ¤t_dir) { + Ok(Some(library)) => library, + + Ok(None) => { + error!("Standard library was empty."); + std::process::exit(1); + } + + Err(error) => { + let missing_files: Vec<_> = config + .std + .split('+') + .filter(|name| { + !PathBuf::from(format!("{name}.yml")).exists() + && !PathBuf::from(format!("{name}.toml")).exists() + }) + .filter(|name| !cfg!(feature = "roblox") || *name != "roblox") + .collect(); + + if !missing_files.is_empty() { + eprintln!( + "`std = \"{}\"`, but some libraries could not be found:", + config.std + ); + + for library_name in missing_files { + eprintln!(" `{library_name}`"); + } + + error!("Could not find all standard library files"); + std::process::exit(1); + } + + error!("Could not collect standard library: {error}"); + std::process::exit(1); + } + }; + + let checker = Arc::new(match Checker::new(config, standard_library) { + Ok(checker) => checker, + Err(error) => { + error!("{error}"); + std::process::exit(1); + } + }); + + let pool = ThreadPool::new(matches.num_threads); + + for filename in &matches.files { + if filename == "-" { + let checker = Arc::clone(&checker); + pool.execute(move || read(&checker, Path::new("-"), io::stdin().lock())); + continue; + } + + match fs::metadata(filename) { + Ok(metadata) => { + if metadata.is_file() { + let checker = Arc::clone(&checker); + let filename = filename.to_owned(); + + pool.execute(move || read_file(&checker, Path::new(&filename))); + } else if metadata.is_dir() { + let glob = match glob::glob(&format!( + "{}/{}", + filename.to_string_lossy(), + matches.pattern, + )) { + Ok(glob) => glob, + Err(error) => { + error!("Invalid glob pattern: {}", error); + return; + } + }; + + for entry in glob { + match entry { + Ok(path) => { + let checker = Arc::clone(&checker); + + pool.execute(move || read_file(&checker, &path)); + } + + Err(error) => { + error!( + "Couldn't open file {}: {}", + filename.to_string_lossy(), + error + ); + } + }; + } + } else { + unreachable!("Somehow got a symlink from the files?"); + } + } + + Err(error) => { + error!( + "Error getting metadata of {}: {}", + filename.to_string_lossy(), + error + ); + + LINT_ERRORS.fetch_add(1, Ordering::SeqCst); + } + }; + } + + pool.join(); + + let (parse_errors, lint_errors, lint_warnings) = ( + PARSE_ERRORS.load(Ordering::SeqCst), + LINT_ERRORS.load(Ordering::SeqCst), + LINT_WARNINGS.load(Ordering::SeqCst), + ); + + if !matches.luacheck && !matches.no_summary { + log_total(parse_errors, lint_errors, lint_warnings).ok(); + } + + if parse_errors + lint_errors + lint_warnings + pool.panic_count() > 0 { + std::process::exit(1); + } +} + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + let mut luacheck = false; + + if let Ok(path) = std::env::current_exe() { + if let Some(stem) = path.file_stem() { + if stem.to_str() == Some("luacheck") { + luacheck = true; + } + } + } + + start(get_opts(luacheck)); + + Ok(()) +} + +// Will attempt to get the options. +// Different from Options::from_args() as if in Luacheck mode +// (either found from --luacheck or from the LUACHECK AtomicBool) +// it will ignore all extra parameters. +// If not in luacheck mode and errors are found, will exit. +fn get_opts(luacheck: bool) -> opts::Options { + get_opts_safe(std::env::args_os().collect::>(), luacheck) + .unwrap_or_else(|err| err.exit()) +} + +fn get_opts_safe(mut args: Vec, luacheck: bool) -> Result { + let mut first_error: Option = None; + + loop { + match opts::Options::from_iter_safe(&args) { + Ok(mut options) => match first_error { + Some(error) => { + if options.luacheck || luacheck { + options.luacheck = true; + break Ok(options); + } else { + break Err(error); + } + } + + None => break Ok(options), + }, + + Err(err) => match err.kind { + clap::ErrorKind::UnknownArgument => { + let bad_arg = + &err.info.as_ref().expect("no info for UnknownArgument")[0].to_owned(); + + args = args + .drain(..) + .filter(|arg| arg.to_string_lossy().split('=').next().unwrap() != bad_arg) + .collect(); + + if first_error.is_none() { + first_error = Some(err); + } + } + + _ => break Err(err), + }, + } + } +} + +#[cfg(feature = "roblox")] +fn generate_roblox_std( + show_deprecated: bool, +) -> Result> { + let (contents, std) = roblox::RobloxGenerator { + std: roblox::RobloxGenerator::base_std(), + show_deprecated, + } + .generate()?; + + fs::File::create("roblox.yml").and_then(|mut file| file.write_all(&contents))?; + + Ok(std) +} + +#[cfg(not(feature = "roblox"))] +fn generate_roblox_std(_: bool) -> Result { + unreachable!("generate_roblox_std called when Roblox feature was not installed!"); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn args(mut args: Vec<&str>) -> Vec { + args.insert(0, "selene"); + args.into_iter().map(OsString::from).collect() + } + + #[test] + fn test_luacheck_opts() { + assert!(get_opts_safe(args(vec!["file"]), false).is_ok()); + assert!(get_opts_safe(args(vec!["--fail", "files"]), false).is_err()); + + match get_opts_safe(args(vec!["--luacheck", "--fail", "files"]), false) { + Ok(opts) => { + assert!(opts.luacheck); + assert_eq!(opts.files, vec![OsString::from("files")]); + } + + Err(err) => { + panic!("selene --luacheck --fail files returned Err: {:?}", err); + } + } + + assert!(get_opts_safe(args(vec!["-", "--formatter=plain"]), true).is_ok()); + + assert!(get_opts_safe(args(vec!["--fail", "files"]), true).is_ok()); + } +} diff --git a/selene/src/opts.rs b/selene/src/opts.rs index 25784da5..90ef2dab 100644 --- a/selene/src/opts.rs +++ b/selene/src/opts.rs @@ -1,9 +1,6 @@ -use std::ffi::OsString; +use std::{ffi::OsString, path::PathBuf}; -use structopt::{ - clap::{arg_enum, AppSettings}, - StructOpt, -}; +use structopt::{clap::arg_enum, StructOpt}; #[derive(Clone, Debug, StructOpt)] #[structopt(rename_all = "kebab-case")] @@ -77,6 +74,8 @@ impl Options { #[derive(Clone, Debug, PartialEq, Eq, StructOpt)] #[structopt(rename_all = "kebab-case")] +// I'm gonna add more than standard library stuff I swear +#[allow(clippy::enum_variant_names)] pub enum Command { #[cfg(feature = "roblox")] GenerateRobloxStd { @@ -84,8 +83,13 @@ pub enum Command { deprecated: bool, }, - #[structopt(setting(AppSettings::Hidden))] - NonExhaustive, + #[cfg(feature = "roblox")] + UpdateRobloxStd, + + UpgradeStd { + #[structopt(parse(from_os_str))] + filename: PathBuf, + }, } arg_enum! { diff --git a/selene/src/roblox/base.toml b/selene/src/roblox/base.toml deleted file mode 100644 index 193bbefd..00000000 --- a/selene/src/roblox/base.toml +++ /dev/null @@ -1,1039 +0,0 @@ -[selene] -base = "lua51" -name = "roblox" - -[selene.structs.EnumItem.Name] -property = true - -[selene.structs.EnumItem.Value] -property = true - -[selene.structs.Event.Connect] -method = true - -[[selene.structs.Event.Connect.args]] -type = "function" - -[selene.structs.Event.Wait] -method = true -args = [] - -[selene.structs.Instance."*"] -any = true - -[[Axes.new.args]] -type = "..." - -[[bit32.arshift.args]] -type = "number" - -[[bit32.arshift.args]] -type = "number" - -[[bit32.band.args]] -type = "..." - -[[bit32.bnot.args]] -type = "number" - -[[bit32.bor.args]] -type = "..." - -[[bit32.btest.args]] -type = "..." - -[[bit32.bxor.args]] -type = "..." - -[[bit32.countlz.args]] -type = "number" - -[[bit32.countrz.args]] -type = "number" - -[[bit32.extract.args]] -type = "number" - -[[bit32.extract.args]] -type = "number" - -[[bit32.extract.args]] -type = "number" -required = false - -[[bit32.replace.args]] -type = "number" - -[[bit32.replace.args]] -type = "number" - -[[bit32.replace.args]] -type = "number" - -[[bit32.replace.args]] -type = "number" -required = false - -[[bit32.lrotate.args]] -type = "number" - -[[bit32.lrotate.args]] -type = "number" - -[[bit32.lshift.args]] -type = "number" - -[[bit32.lshift.args]] -type = "number" - -[[bit32.rrotate.args]] -type = "number" - -[[bit32.rrotate.args]] -type = "number" - -[[bit32.rshift.args]] -type = "number" - -[[bit32.rshift.args]] -type = "number" - -[[BrickColor.new.args]] -type = "any" - -[[BrickColor.new.args]] -type = "number" -required = false - -[[BrickColor.new.args]] -type = "number" -required = false - -[[BrickColor.palette.args]] -type = "number" - -[BrickColor.random] -args = [] - -# BrickColor colors -[BrickColor.Black] -args = [] - -[BrickColor.Blue] -args = [] - -[BrickColor.DarkGray] -args = [] - -[BrickColor.Gray] -args = [] - -[BrickColor.Green] -args = [] - -[BrickColor.Red] -args = [] - -[BrickColor.White] -args = [] - -[BrickColor.Yellow] -args = [] - -[[CFrame.Angles.args]] -type = "number" -required = false - -[[CFrame.Angles.args]] -type = "number" -required = false - -[[CFrame.Angles.args]] -type = "number" -required = false - -[CFrame.identity] -property = true - -[[CFrame.new.args]] -type = "any" -required = false - -[[CFrame.new.args]] -type = "any" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.new.args]] -type = "number" -required = false - -[[CFrame.lookAt.args]] -type = { display = "Vector3" } - -[[CFrame.lookAt.args]] -type = { display = "Vector3" } - -[[CFrame.lookAt.args]] -type = { display = "Vector3" } -required = false - -[[CFrame.fromAxisAngle.args]] -type = { display = "Vector3" } - -[[CFrame.fromAxisAngle.args]] -type = "number" - -[[CFrame.fromEulerAnglesXYZ.args]] -type = "number" - -[[CFrame.fromEulerAnglesXYZ.args]] -type = "number" - -[[CFrame.fromEulerAnglesXYZ.args]] -type = "number" - -[[CFrame.fromEulerAnglesYXZ.args]] -type = "number" - -[[CFrame.fromEulerAnglesYXZ.args]] -type = "number" - -[[CFrame.fromEulerAnglesYXZ.args]] -type = "number" - -[[CFrame.fromMatrix.args]] -type = { display = "Vector3" } - -[[CFrame.fromMatrix.args]] -type = { display = "Vector3" } - -[[CFrame.fromMatrix.args]] -type = { display = "Vector3" } - -[[CFrame.fromMatrix.args]] -type = { display = "Vector3" } -required = false - -[[CFrame.fromOrientation.args]] -type = "number" - -[[CFrame.fromOrientation.args]] -type = "number" - -[[CFrame.fromOrientation.args]] -type = "number" - -[[Color3.fromRGB.args]] -type = "number" - -[[Color3.fromRGB.args]] -type = "number" - -[[Color3.fromRGB.args]] -type = "number" - -[[Color3.fromHSV.args]] -type = "number" - -[[Color3.fromHSV.args]] -type = "number" - -[[Color3.fromHSV.args]] -type = "number" - -[[Color3.fromHex.args]] -type = "string" - -[[Color3.new.args]] -type = "number" -required = false - -[[Color3.new.args]] -type = "number" -required = false - -[[Color3.new.args]] -type = "number" -required = false - -[[Color3.toHSV.args]] -type = { display = "Color3" } - -[[ColorSequence.new.args]] -type = "any" - -[[ColorSequence.new.args]] -type = { display = "Color3" } -required = false - -[[ColorSequenceKeypoint.new.args]] -type = "number" - -[[ColorSequenceKeypoint.new.args]] -type = { display = "Color3" } - -[[coroutine.close.args]] -type = { display = "thread" } - -[coroutine.isyieldable] -args = [] - -[DateTime.now] -args = [] - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromLocalTime.args]] -required = false -type = "number" - -[[DateTime.fromIsoDate.args]] -type = "string" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUniversalTime.args]] -required = false -type = "number" - -[[DateTime.fromUnixTimestamp.args]] -type = "number" - -[[DateTime.fromUnixTimestampMillis.args]] -type = "number" - -[[debug.info.args]] -type = "any" - -[[debug.info.args]] -type = "any" - -[[debug.info.args]] -type = "string" -required = false - -[[debug.profilebegin.args]] -type = "string" - -[debug.profileend] -args = [] - -[debug.resetmemorycategory] -args = [] - -[[debug.setmemorycategory.args]] -type = "string" - -[[delay.args]] -type = "number" - -[[delay.args]] -type = "function" - -[DebuggerManager] -args = [] - -[[DockWidgetPluginGuiInfo.new.args]] -type = { display = "InitialDockState" } -required = false - -[[DockWidgetPluginGuiInfo.new.args]] -type = "bool" -required = false - -[[DockWidgetPluginGuiInfo.new.args]] -type = "bool" -required = false - -[[DockWidgetPluginGuiInfo.new.args]] -type = "number" -required = false - -[[DockWidgetPluginGuiInfo.new.args]] -type = "number" -required = false - -[[DockWidgetPluginGuiInfo.new.args]] -type = "number" -required = false - -[[DockWidgetPluginGuiInfo.new.args]] -type = "number" -required = false - -[elapsedTime] -args=[] - -[Enum.GetEnums] -method = true -args = [] - -[[error.args]] -type = "any" -required = "Erroring without an explanation is unhelpful to users." - -[[error.args]] -type = "number" -required = false - -[[Faces.new.args]] -type = "..." - -[gcinfo] -args = [] - -[[Instance.new.args]] -type = "string" # This is changed at generate time - -[[math.clamp.args]] -type = "number" - -[[math.clamp.args]] -type = "number" - -[[math.clamp.args]] -type = "number" - -[[math.log.args]] -type = "number" - -[[math.log.args]] -type = "number" -required = false - -[[math.round.args]] -type = "number" - -[[math.sign.args]] -type = "number" - -[[math.noise.args]] -type = "number" - -[[math.noise.args]] -type = "number" -required = false - -[[math.noise.args]] -type = "number" -required = false - -[[NumberRange.new.args]] -type = "number" - -[[NumberRange.new.args]] -required = false -type = "number" - -[[NumberSequence.new.args]] -type = "any" - -[[NumberSequence.new.args]] -type = "number" -required = false - -[[NumberSequenceKeypoint.new.args]] -type = "number" - -[[NumberSequenceKeypoint.new.args]] -type = "number" - -[[NumberSequenceKeypoint.new.args]] -type = "number" -required = false - -[OverlapParams.new] -args = [] - -[[PathWaypoint.new.args]] -type = { display = "Vector3" } -required = false - -[[PathWaypoint.new.args]] -type = { display = "PathWaypointAction" } -required = false - -[[PhysicalProperties.new.args]] -type = "any" - -[[PhysicalProperties.new.args]] -type = "number" -required = false - -[[PhysicalProperties.new.args]] -type = "number" -required = false - -[[PhysicalProperties.new.args]] -type = "number" -required = false - -[[PhysicalProperties.new.args]] -type = "number" -required = false - -[[Random.new.args]] -type = "number" -required = false - -[[Ray.new.args]] -type = { display = "Vector3" } - -[[Ray.new.args]] -type = { display = "Vector3" } - -[RaycastParams.new] -args = [] - -[[Rect.new.args]] -type = "any" - -[[Rect.new.args]] -type = "any" - -[[Rect.new.args]] -type = "number" -required = false - -[[Rect.new.args]] -type = "number" -required = false - -[[Region3.new.args]] -type = { display = "Vector3" } - -[[Region3.new.args]] -type = { display = "Vector3"} - -[[Region3int16.new.args]] -type = { display = "Vector3" } -required = false - -[[Region3int16.new.args]] -type = { display = "Vector3"} -required = false - -[[require.args]] -type = "number" - -[settings] -args = [] - -[shared] -property = true -writable = "new-fields" - -[[spawn.args]] -type = "function" - -[[string.split.args]] -type = "string" - -[[string.split.args]] -type = "string" -required = false - -[[string.pack.args]] -type = "string" - -[[string.pack.args]] -type = "..." - -[[string.packsize.args]] -type = "string" - -[[string.unpack.args]] -type = "string" - -[[string.unpack.args]] -type = "string" - -[[string.unpack.args]] -type = "number" -required = false - -[[table.clear.args]] -type = "table" - -[[table.clone.args]] -type = "table" - -[[table.create.args]] -type = "number" - -[[table.create.args]] -type = "any" -required = false - -[[table.find.args]] -type = "table" - -[[table.find.args]] -type = "any" - -[[table.find.args]] -type = "number" -required = false - -[[table.freeze.args]] -type = "table" - -[[table.isfrozen.args]] -type = "table" - -[[table.move.args]] -type = "table" - -[[table.move.args]] -type = "number" - -[[table.move.args]] -type = "number" - -[[table.move.args]] -type = "number" - -[[table.move.args]] -type = "table" -required = false - -[[table.pack.args]] -type = "..." - -[[table.unpack.args]] -type = "table" - -[[table.unpack.args]] -type = "number" -required = false - -[[table.unpack.args]] -type = "number" -required = false - -[[task.cancel.args]] -type = { display = "thread" } - -[[task.defer.args]] -type = "function" - -[[task.defer.args]] -required = false -type = "..." - -[[task.delay.args]] -required = false -type = "number" - -[[task.delay.args]] -type = "function" - -[[task.delay.args]] -required = false -type = "..." - -[task.desynchronize] -args = [] - -[[task.spawn.args]] -type = "function" - -[[task.spawn.args]] -required = false -type = "..." - -[task.synchronize] -args = [] - -[[task.wait.args]] -required = false -type = "number" - -[tick] -args = [] - -[time] -args = [] - -[[TweenInfo.new.args]] -type = "number" -required = false - -[[TweenInfo.new.args]] -type = { display = "EasingStyle" } -required = false - -[[TweenInfo.new.args]] -type = { display = "EasingDirection" } -required = false - -[[TweenInfo.new.args]] -type = "number" -required = false - -[[TweenInfo.new.args]] -type = "bool" -required = false - -[[TweenInfo.new.args]] -type = "number" -required = false - -[[typeof.args]] -type = "any" - -[[UDim.new.args]] -type = "number" -required = false - -[[UDim.new.args]] -type = "number" -required = false - -[[UDim2.new.args]] -type = "any" -required = false - -[[UDim2.new.args]] -type = "any" -required = false - -[[UDim2.new.args]] -type = "number" -required = false - -[[UDim2.new.args]] -type = "number" -required = false - -[[UDim2.fromOffset.args]] -type = "number" -required = "use UDim2.new() if you want an empty UDim2" - -[[UDim2.fromOffset.args]] -type = "number" -required = false - -[[UDim2.fromScale.args]] -type = "number" -required = "use UDim2.new() if you want an empty UDim2" - -[[UDim2.fromScale.args]] -type = "number" -required = false - -[UserSettings] -args = [] - -[[utf8.char.args]] -type = "number" -required = "utf8.char should be used with an argument despite it not throwing" - -[[utf8.char.args]] -type = "..." -required = false - -[utf8.charpattern] -property = true - -[[utf8.codepoint.args]] -type = "string" - -[[utf8.codepoint.args]] -type = "number" -required = false - -[[utf8.codepoint.args]] -type = "number" -required = false - -[[utf8.codes.args]] -type = "string" - -[[utf8.graphemes.args]] -type = "string" - -[[utf8.graphemes.args]] -type = "number" -required = false - -[[utf8.graphemes.args]] -type = "number" -required = false - -[[utf8.len.args]] -type = "string" - -[[utf8.len.args]] -type = "number" -required = false - -[[utf8.len.args]] -type = "number" -required = false - -[[utf8.nfcnormalize.args]] -type = "string" - -[[utf8.nfdnormalize.args]] -type = "string" - -[[utf8.offset.args]] -type = "string" - -[[utf8.offset.args]] -type = "number" -required = false - -[[utf8.offset.args]] -type = "number" -required = false - -[[Vector2.new.args]] -type = "number" -required = false - -[[Vector2.new.args]] -type = "number" -required = false - -[Vector2.one] -property = true - -[Vector2.xAxis] -property = true - -[Vector2.yAxis] -property = true - -[Vector2.zero] -property = true - -[[Vector2int16.new.args]] -type = "number" -required = false - -[[Vector2int16.new.args]] -type = "number" -required = false - -[[Vector3.FromAxis.args]] -type = { display = "Axis" } - -[[Vector3.FromNormalId.args]] -type = { display = "NormalId" } - -[[Vector3.new.args]] -type = "number" -required = false - -[[Vector3.new.args]] -type = "number" -required = false - -[[Vector3.new.args]] -type = "number" -required = false - -[Vector3.one] -property = true - -[Vector3.xAxis] -property = true - -[Vector3.yAxis] -property = true - -[Vector3.zAxis] -property = true - -[Vector3.zero] -property = true - -[[Vector3int16.new.args]] -type = "number" -required = false - -[[Vector3int16.new.args]] -type = "number" -required = false - -[[Vector3int16.new.args]] -type = "number" -required = false - -[[warn.args]] -type = "string" - -[[warn.args]] -type = "..." -required = false - -[[wait.args]] -type = "number" -required = false - -# Removed stuff -[[collectgarbage.args]] -type = ["count"] - -[dofile] -removed = true - -[load] -removed = true - -[loadfile] -removed = true - -[module] -removed = true - -[debug.debug] -removed = true - -[debug.getfenv] -removed = true - -[debug.gethook] -removed = true - -[debug.getinfo] -removed = true - -[debug.getlocal] -removed = true - -[debug.getmetatable] -removed = true - -[debug.getregistry] -removed = true - -[debug.getupvalue] -removed = true - -[debug.setfenv] -removed = true - -[debug.sethook] -removed = true - -[debug.setlocal] -removed = true - -[debug.setmetatable] -removed = true - -[debug.setupvalue] -removed = true - -[io] -removed = true - -[string.dump] -removed = true - -[os.execute] -removed = true - -[os.exit] -removed = true - -[os.getenv] -removed = true - -[os.remove] -removed = true - -[os.rename] -removed = true - -[os.setlocale] -removed = true - -[os.tmpname] -removed = true - -[package] -removed = true diff --git a/selene/src/roblox/base.yml b/selene/src/roblox/base.yml new file mode 100644 index 00000000..22e67b03 --- /dev/null +++ b/selene/src/roblox/base.yml @@ -0,0 +1,740 @@ +--- +base: lua51 +name: roblox +globals: + Axes.new: + args: + - type: "..." + BrickColor.Black: + args: [] + BrickColor.Blue: + args: [] + BrickColor.DarkGray: + args: [] + BrickColor.Gray: + args: [] + BrickColor.Green: + args: [] + BrickColor.Red: + args: [] + BrickColor.White: + args: [] + BrickColor.Yellow: + args: [] + BrickColor.new: + args: + - type: any + - required: false + type: number + - required: false + type: number + BrickColor.palette: + args: + - type: number + BrickColor.random: + args: [] + CFrame.Angles: + args: + - required: false + type: number + - required: false + type: number + - required: false + type: number + CFrame.fromAxisAngle: + args: + - type: + display: Vector3 + - type: number + CFrame.fromEulerAnglesXYZ: + args: + - type: number + - type: number + - type: number + CFrame.fromEulerAnglesYXZ: + args: + - type: number + - type: number + - type: number + CFrame.fromMatrix: + args: + - type: + display: Vector3 + - type: + display: Vector3 + - type: + display: Vector3 + - required: false + type: + display: Vector3 + CFrame.fromOrientation: + args: + - type: number + - type: number + - type: number + CFrame.identity: + property: read-only + CFrame.lookAt: + args: + - type: + display: Vector3 + - type: + display: Vector3 + - required: false + type: + display: Vector3 + CFrame.new: + args: + - required: false + type: any + - required: false + type: any + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + Color3.fromHSV: + args: + - type: number + - type: number + - type: number + Color3.fromHex: + args: + - type: string + Color3.fromRGB: + args: + - type: number + - type: number + - type: number + Color3.new: + args: + - required: false + type: number + - required: false + type: number + - required: false + type: number + Color3.toHSV: + args: + - type: + display: Color3 + ColorSequence.new: + args: + - type: any + - required: false + type: + display: Color3 + ColorSequenceKeypoint.new: + args: + - type: number + - type: + display: Color3 + DateTime.fromIsoDate: + args: + - type: string + DateTime.fromLocalTime: + args: + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + DateTime.fromUniversalTime: + args: + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + DateTime.fromUnixTimestamp: + args: + - type: number + DateTime.fromUnixTimestampMillis: + args: + - type: number + DateTime.now: + args: [] + DebuggerManager: + args: [] + DockWidgetPluginGuiInfo.new: + args: + - required: false + type: + display: InitialDockState + - required: false + type: bool + - required: false + type: bool + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + Enum.GetEnums: + args: [] + method: true + Faces.new: + args: + - type: "..." + Instance.new: + args: + - type: string + NumberRange.new: + args: + - type: number + - required: false + type: number + NumberSequence.new: + args: + - type: any + - required: false + type: number + NumberSequenceKeypoint.new: + args: + - type: number + - type: number + - required: false + type: number + OverlapParams.new: + args: [] + PathWaypoint.new: + args: + - required: false + type: + display: Vector3 + - required: false + type: + display: PathWaypointAction + PhysicalProperties.new: + args: + - type: any + - required: false + type: number + - required: false + type: number + - required: false + type: number + - required: false + type: number + Random.new: + args: + - required: false + type: number + Ray.new: + args: + - type: + display: Vector3 + - type: + display: Vector3 + RaycastParams.new: + args: [] + Rect.new: + args: + - type: any + - type: any + - required: false + type: number + - required: false + type: number + Region3.new: + args: + - type: + display: Vector3 + - type: + display: Vector3 + Region3int16.new: + args: + - required: false + type: + display: Vector3 + - required: false + type: + display: Vector3 + TweenInfo.new: + args: + - required: false + type: number + - required: false + type: + display: EasingStyle + - required: false + type: + display: EasingDirection + - required: false + type: number + - required: false + type: bool + - required: false + type: number + UDim.new: + args: + - required: false + type: number + - required: false + type: number + UDim2.fromOffset: + args: + - required: use UDim2.new() if you want an empty UDim2 + type: number + - required: false + type: number + UDim2.fromScale: + args: + - required: use UDim2.new() if you want an empty UDim2 + type: number + - required: false + type: number + UDim2.new: + args: + - required: false + type: any + - required: false + type: any + - required: false + type: number + - required: false + type: number + UserSettings: + args: [] + Vector2.new: + args: + - required: false + type: number + - required: false + type: number + Vector2.one: + property: read-only + Vector2.xAxis: + property: read-only + Vector2.yAxis: + property: read-only + Vector2.zero: + property: read-only + Vector2int16.new: + args: + - required: false + type: number + - required: false + type: number + Vector3.FromAxis: + args: + - type: + display: Axis + Vector3.FromNormalId: + args: + - type: + display: NormalId + Vector3.new: + args: + - required: false + type: number + - required: false + type: number + - required: false + type: number + Vector3.one: + property: read-only + Vector3.xAxis: + property: read-only + Vector3.yAxis: + property: read-only + Vector3.zAxis: + property: read-only + Vector3.zero: + property: read-only + Vector3int16.new: + args: + - required: false + type: number + - required: false + type: number + - required: false + type: number + bit32.arshift: + args: + - type: number + - type: number + bit32.band: + args: + - type: "..." + bit32.bnot: + args: + - type: number + bit32.bor: + args: + - type: "..." + bit32.btest: + args: + - type: "..." + bit32.bxor: + args: + - type: "..." + bit32.countlz: + args: + - type: number + bit32.countrz: + args: + - type: number + bit32.extract: + args: + - type: number + - type: number + - required: false + type: number + bit32.lrotate: + args: + - type: number + - type: number + bit32.lshift: + args: + - type: number + - type: number + bit32.replace: + args: + - type: number + - type: number + - type: number + - required: false + type: number + bit32.rrotate: + args: + - type: number + - type: number + bit32.rshift: + args: + - type: number + - type: number + collectgarbage: + args: + - type: + - count + coroutine.close: + args: + - type: + display: thread + coroutine.isyieldable: + args: [] + debug.debug: + removed: true + debug.getfenv: + removed: true + debug.gethook: + removed: true + debug.getinfo: + removed: true + debug.getlocal: + removed: true + debug.getmetatable: + removed: true + debug.getregistry: + removed: true + debug.getupvalue: + removed: true + debug.info: + args: + - type: any + - type: any + - required: false + type: string + debug.profilebegin: + args: + - type: string + debug.profileend: + args: [] + debug.resetmemorycategory: + args: [] + debug.setfenv: + removed: true + debug.sethook: + removed: true + debug.setlocal: + removed: true + debug.setmemorycategory: + args: + - type: string + debug.setmetatable: + removed: true + debug.setupvalue: + removed: true + delay: + args: + - type: number + - type: function + dofile: + removed: true + elapsedTime: + args: [] + error: + args: + - required: Erroring without an explanation is unhelpful to users. + type: any + - required: false + type: number + gcinfo: + args: [] + io: + removed: true + load: + removed: true + loadfile: + removed: true + math.clamp: + args: + - type: number + - type: number + - type: number + math.log: + args: + - type: number + - required: false + type: number + math.noise: + args: + - type: number + - required: false + type: number + - required: false + type: number + math.round: + args: + - type: number + math.sign: + args: + - type: number + module: + removed: true + os.execute: + removed: true + os.exit: + removed: true + os.getenv: + removed: true + os.remove: + removed: true + os.rename: + removed: true + os.setlocale: + removed: true + os.tmpname: + removed: true + package: + removed: true + require: + args: + - type: number + settings: + args: [] + shared: + property: new-fields + spawn: + args: + - type: function + string.dump: + removed: true + string.pack: + args: + - type: string + - type: "..." + string.packsize: + args: + - type: string + string.split: + args: + - type: string + - required: false + type: string + string.unpack: + args: + - type: string + - type: string + - required: false + type: number + table.clear: + args: + - type: table + table.clone: + args: + - type: table + table.create: + args: + - type: number + - required: false + type: any + table.find: + args: + - type: table + - type: any + - required: false + type: number + table.freeze: + args: + - type: table + table.isfrozen: + args: + - type: table + table.move: + args: + - type: table + - type: number + - type: number + - type: number + - required: false + type: table + table.pack: + args: + - type: "..." + table.unpack: + args: + - type: table + - required: false + type: number + - required: false + type: number + task.cancel: + args: + - type: + display: thread + task.defer: + args: + - type: function + - required: false + type: "..." + task.delay: + args: + - required: false + type: number + - type: function + - required: false + type: "..." + task.desynchronize: + args: [] + task.spawn: + args: + - type: function + - required: false + type: "..." + task.synchronize: + args: [] + task.wait: + args: + - required: false + type: number + tick: + args: [] + time: + args: [] + typeof: + args: + - type: any + utf8.char: + args: + - required: utf8.char should be used with an argument despite it not throwing + type: number + - required: false + type: "..." + utf8.charpattern: + property: read-only + utf8.codepoint: + args: + - type: string + - required: false + type: number + - required: false + type: number + utf8.codes: + args: + - type: string + utf8.graphemes: + args: + - type: string + - required: false + type: number + - required: false + type: number + utf8.len: + args: + - type: string + - required: false + type: number + - required: false + type: number + utf8.nfcnormalize: + args: + - type: string + utf8.nfdnormalize: + args: + - type: string + utf8.offset: + args: + - type: string + - required: false + type: number + - required: false + type: number + wait: + args: + - required: false + type: number + warn: + args: + - type: string + - required: false + type: "..." +structs: + Enum: + GetEnumItems: + method: true + args: [] + EnumItem: + Name: + property: read-only + Value: + property: read-only + Event: + Connect: + args: + - type: function + method: true + Wait: + args: [] + method: true + Instance: + "*": + any: true diff --git a/selene/src/roblox/collect_std.rs b/selene/src/roblox/collect_std.rs new file mode 100644 index 00000000..5a62ec0c --- /dev/null +++ b/selene/src/roblox/collect_std.rs @@ -0,0 +1,130 @@ +use std::{ + fs, + io::BufReader, + path::{Path, PathBuf}, +}; + +use chrono::TimeZone; +use color_eyre::eyre::Context; +use selene_lib::{standard_library::StandardLibrary, CheckerConfig, RobloxStdSource}; + +use super::RobloxGenerator; + +// Someday, this can be a global config +const HOUR_CACHE: i64 = 6; + +pub fn collect_roblox_standard_library( + config: &CheckerConfig, + current_directory: &Path, +) -> color_eyre::Result { + let (cached_library, output_directory, output_location) = match config.roblox_std_source { + RobloxStdSource::Floating => { + let floating_file_directory = floating_file_directory()?; + let floating_file = floating_file_directory.join("roblox.yml"); + + if floating_file.exists() { + let mut library: StandardLibrary = serde_yaml::from_reader(BufReader::new( + fs::File::open(&floating_file) + .context("Culd not open the floating roblox standard library.")?, + ))?; + + if (chrono::Local::now() + - chrono::Local.timestamp(library.last_updated.unwrap_or(0), 0)) + .num_hours() + < HOUR_CACHE + { + if let Some(base) = &library.base { + let base_library = StandardLibrary::from_name(base) + .expect("Roblox standard library had an invalid base"); + + library.extend(base_library); + } + + return Ok(library); + } + + (Some(library), floating_file_directory, floating_file) + } else { + (None, floating_file_directory, floating_file) + } + } + + // If we get to this stage, then it means the search for roblox.yml + // already failed. + RobloxStdSource::Pinned => ( + None, + current_directory.to_owned(), + current_directory.join("roblox.yml"), + ), + }; + + let generated_std = RobloxGenerator { + std: RobloxGenerator::base_std(), + show_deprecated: false, + } + .generate(); + + match (generated_std, cached_library) { + (Ok((contents, new_library)), _) => { + fs::create_dir_all(&output_directory).with_context(|| { + format!( + "Could not create the directory for the floating roblox standard library at {}", + output_directory.display() + ) + })?; + + fs::write(&output_location, contents).with_context(|| { + format!( + "Could not write the Roblox standard library to {}", + output_location.display() + ) + })?; + + Ok(new_library) + } + + (Err(error), Some(cached_library)) => { + crate::error(&format!( + "There was an error generating a new Roblox standard library: {error}\nUsing the cached one instead.", + )); + + Ok(cached_library) + } + + (Err(error), None) => Err(error), + } +} + +fn floating_file_directory() -> color_eyre::Result { + match dirs::cache_dir() { + Some(cache_dir) => Ok(cache_dir.join("selene")), + None => color_eyre::eyre::bail!("your platform is not supported"), + } +} + +pub fn update_roblox_std() -> color_eyre::Result<()> { + let (contents, _) = RobloxGenerator { + std: RobloxGenerator::base_std(), + show_deprecated: false, + } + .generate()?; + + let output_directory = floating_file_directory()?; + let output_location = output_directory.join("roblox.yml"); + + fs::create_dir_all(&output_directory).with_context(|| { + format!( + "Could not create the directory for the floating roblox standard library at {}", + output_directory.display() + ) + })?; + + fs::write(&output_location, contents).with_context(|| { + format!( + "Could not write the Roblox standard library to {}", + output_location.display() + ) + })?; + + Ok(()) +} diff --git a/selene/src/roblox.rs b/selene/src/roblox/generate_std.rs similarity index 63% rename from selene/src/roblox.rs rename to selene/src/roblox/generate_std.rs index 7873b771..aaefd08c 100644 --- a/selene/src/roblox.rs +++ b/selene/src/roblox/generate_std.rs @@ -1,359 +1,342 @@ -use chrono::Local; -use std::{collections::BTreeMap, fmt, io::Write}; - -mod api; - -use api::*; -use selene_lib::standard_library::*; - -const API_DUMP: &str = - "https://raw.githubusercontent.com/CloneTrooper1019/Roblox-Client-Tracker/roblox/API-Dump.json"; - -pub struct RobloxGenerator { - pub std: StandardLibrary, - pub show_deprecated: bool, -} - -pub enum GenerateError { - Http(reqwest::Error), - Io(std::io::Error), - TomlDe(toml::de::Error), - TomlSer(toml::ser::Error), -} - -impl fmt::Display for GenerateError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - match self { - GenerateError::Http(error) => write!(formatter, "HTTP error: {}", error), - GenerateError::Io(error) => write!(formatter, "IO error: {}", error), - GenerateError::TomlDe(error) => { - write!(formatter, "TOML deserialization error: {}", error) - } - GenerateError::TomlSer(error) => { - write!(formatter, "TOML serialization error: {}", error) - } - } - } -} - -impl RobloxGenerator { - pub fn generate(mut self) -> Result<(Vec, StandardLibrary), GenerateError> { - let api: ApiDump = reqwest::get(API_DUMP) - .and_then(|mut response| response.json()) - .map_err(GenerateError::Http)?; - - self.write_class(&api, "game", "DataModel"); - self.write_class(&api, "plugin", "Plugin"); - self.write_class(&api, "script", "Script"); - self.write_class(&api, "workspace", "Workspace"); - - self.write_enums(&api); - self.write_instance_new(&api); - self.write_get_service(&api); - - self.deprecated_event_methods(); - - let mut bytes = Vec::new(); - - writeln!( - bytes, - "# This file was @generated by generate-roblox-std at {}", - Local::now() - ) - .map_err(GenerateError::Io)?; - - write!( - bytes, - "{}", - toml::to_string(&self.std).map_err(GenerateError::TomlSer)? - ) - .map_err(GenerateError::Io)?; - - self.std.extend( - StandardLibrary::from_name(self.std.meta.as_ref().unwrap().base.as_ref().unwrap()) - .unwrap(), - ); - - self.std.inflate(); - - Ok((bytes, self.std)) - } - - pub fn base_std() -> Result { - toml::from_str(include_str!("./roblox/base.toml")).map_err(GenerateError::TomlDe) - } - - fn write_class(&mut self, api: &api::ApiDump, global_name: &str, class_name: &str) { - self.write_class_struct(api, class_name); - self.std - .globals - .insert(global_name.to_owned(), Field::Struct(class_name.to_owned())); - } - - fn write_class_struct(&mut self, api: &api::ApiDump, class_name: &str) { - let structs = self.std.meta.as_mut().unwrap().structs.as_mut().unwrap(); - if structs.contains_key(class_name) { - return; - } - structs.insert(class_name.to_owned(), BTreeMap::new()); - - let mut table = BTreeMap::new(); - table.insert("*".to_owned(), Field::Struct("Instance".to_owned())); - self.write_class_members(api, &mut table, class_name); - - let structs = self.std.meta.as_mut().unwrap().structs.as_mut().unwrap(); - structs.insert(class_name.to_owned(), table); - } - - fn write_class_members( - &mut self, - api: &api::ApiDump, - table: &mut BTreeMap, - class_name: &str, - ) { - let class = api.classes.iter().find(|c| c.name == class_name).unwrap(); - - for member in &class.members { - let (name, tags, field) = match &member { - ApiMember::Callback { name, tags } => ( - name, - tags, - Some(Field::Property { - writable: Some(Writable::Overridden), - }), - ), - - ApiMember::Event { name, tags } => { - (name, tags, Some(Field::Struct("Event".to_owned()))) - } - - ApiMember::Function { - name, - tags, - parameters, - } => ( - name, - tags, - Some( - FunctionBehavior { - // TODO: Roblox doesn't tell us which parameters are nillable or not - // So results from these are regularly wrong - // The best solution is a manual patch for every method we *know* is nillable - // e.g. WaitForChild - // We can also let some parameters be required in the middle, and fix unused_variable to accept them - - // arguments: parameters - // .iter() - // .map(|param| Argument { - // required: if param.default.is_some() { - // Required::NotRequired - // } else { - // Required::Required(None) - // }, - // argument_type: match ¶m.parameter_type { - // ApiValueType::Class { name } => { - // ArgumentType::Display(name.to_owned()) - // } - // - // ApiValueType::DataType { value } => match value { - // ApiDataType::Content => ArgumentType::String, - // ApiDataType::Other(other) => { - // ArgumentType::Display(other.to_owned()) - // } - // }, - // - // ApiValueType::Group { value } => match value { - // ApiGroupType::Table => ArgumentType::Table, - // ApiGroupType::Tuple => ArgumentType::Vararg, - // ApiGroupType::Variant => ArgumentType::Any, - // }, - // - // ApiValueType::Primitive { value } => match value { - // ApiPrimitiveType::Bool => ArgumentType::Bool, - // ApiPrimitiveType::Double - // | ApiPrimitiveType::Float - // | ApiPrimitiveType::Int - // | ApiPrimitiveType::Int64 => ArgumentType::Number, - // ApiPrimitiveType::String => ArgumentType::String, - // }, - // - // ApiValueType::Other { name } => { - // ArgumentType::Display(name.to_owned()) - // } - // }, - // }) - // .collect(), - arguments: parameters - .iter() - .map(|_| Argument { - argument_type: ArgumentType::Any, - required: Required::NotRequired, - }) - .collect(), - method: true, - } - .into(), - ), - ), - - ApiMember::Property { - name, - tags, - security, - value_type, - } => (name, tags, { - if *security == ApiPropertySecurity::default() { - let empty = Vec::new(); - let tags: &Vec = match tags { - Some(tags) => tags, - None => &empty, - }; - - let default_field = Some(Field::Property { - writable: if tags.contains(&"ReadOnly".to_string()) { - None - } else { - Some(Writable::Overridden) - }, - }); - - match &value_type { - ApiValueType::Class { name } => { - self.write_class_struct(api, name); - Some(Field::Struct(name.to_owned())) - } - - ApiValueType::DataType { value } => { - // See comment on `has_custom_methods` for why we're taking - // such a lax approach here. - if value.has_custom_methods() { - Some(Field::Any) - } else { - default_field - } - } - - _ => default_field, - } - } else { - None - } - }), - }; - - let empty = Vec::new(); - let tags: &Vec = match tags { - Some(tags) => tags, - None => &empty, - }; - - if !self.show_deprecated && tags.contains(&"Deprecated".to_owned()) { - continue; - } - - if let Some(field) = field { - table.insert(name.to_owned(), field); - } - } - - if class.superclass != "<<>>" { - self.write_class_members(api, table, &class.superclass); - } - } - - fn write_enums(&mut self, api: &api::ApiDump) { - let mut children = BTreeMap::new(); - - for enuhm in &api.enums { - let mut enum_table = BTreeMap::new(); - enum_table.insert( - "GetEnumItems".to_owned(), - FunctionBehavior { - arguments: vec![], - method: true, - } - .into(), - ); - - for item in &enuhm.items { - enum_table.insert(item.name.to_owned(), Field::Struct("EnumItem".to_owned())); - } - - children.insert(enuhm.name.to_owned(), enum_table.into()); - } - - self.std.globals.insert("Enum".to_owned(), children.into()); - } - - fn write_instance_new(&mut self, api: &api::ApiDump) { - let instance_names = api - .classes - .iter() - .filter_map(|class| { - if !class.tags.contains(&"NotCreatable".to_owned()) { - Some(class.name.to_owned()) - } else { - None - } - }) - .collect(); - - let mut instance = self.std.globals.get_mut("Instance").unwrap(); - - if let Field::Complex { table, .. } = &mut instance { - *table.get_mut("new").unwrap() = FunctionBehavior { - arguments: vec![Argument { - argument_type: ArgumentType::Constant(instance_names), - required: Required::Required(None), - }], - method: false, - } - .into(); - } else { - unreachable!() - } - } - - fn write_get_service(&mut self, api: &api::ApiDump) { - let service_names = api - .classes - .iter() - .filter_map(|class| { - if class.tags.contains(&"Service".to_owned()) { - Some(class.name.to_owned()) - } else { - None - } - }) - .collect(); - - let meta = self.std.meta.as_mut().unwrap(); - let structs = meta.structs.as_mut().unwrap(); - let data_model = structs.get_mut("DataModel").unwrap(); - - *data_model.get_mut("GetService").unwrap() = FunctionBehavior { - arguments: vec![Argument { - argument_type: ArgumentType::Constant(service_names), - required: Required::Required(None), - }], - method: true, - } - .into(); - } - - fn deprecated_event_methods(&mut self) { - if !self.show_deprecated { - return; - } - - let structs = self.std.meta.as_mut().unwrap().structs.as_mut().unwrap(); - let event_struct = structs.get_mut("Event").unwrap(); - let (connect, wait) = ( - event_struct["Connect"].clone(), - event_struct["Wait"].clone(), - ); - - event_struct.insert("connect".to_owned(), connect); - event_struct.insert("wait".to_owned(), wait); - } -} +use chrono::Local; +use color_eyre::eyre::Context; +use std::{collections::BTreeMap, io::Write}; + +use super::api::*; +use selene_lib::standard_library::*; + +const API_DUMP: &str = + "https://raw.githubusercontent.com/CloneTrooper1019/Roblox-Client-Tracker/roblox/API-Dump.json"; + +pub struct RobloxGenerator { + pub std: StandardLibrary, + pub show_deprecated: bool, +} + +impl RobloxGenerator { + pub fn generate(mut self) -> color_eyre::Result<(Vec, StandardLibrary)> { + let api: ApiDump = ureq::get(API_DUMP) + .call() + .context("error when getting API dump")? + .into_json() + .context("error when parsing API dump")?; + + self.write_class(&api, "game", "DataModel"); + self.write_class(&api, "plugin", "Plugin"); + self.write_class(&api, "script", "Script"); + self.write_class(&api, "workspace", "Workspace"); + + self.write_enums(&api); + self.write_instance_new(&api); + self.write_get_service(&api); + + self.deprecated_event_methods(); + + let mut bytes = Vec::new(); + + let time = Local::now(); + self.std.last_updated = Some(time.timestamp()); + + writeln!( + bytes, + "# This file was @generated by generate-roblox-std at {time}", + )?; + + write!(bytes, "{}", serde_yaml::to_string(&self.std)?)?; + + self.std + .extend(StandardLibrary::from_name(self.std.base.as_ref().unwrap()).unwrap()); + + Ok((bytes, self.std)) + } + + pub fn base_std() -> StandardLibrary { + serde_yaml::from_str(include_str!("./base.yml")) + .expect("Roblox base.yml was an invalid standard library") + } + + fn write_class(&mut self, api: &ApiDump, global_name: &str, class_name: &str) { + self.write_class_struct(api, class_name); + self.std.globals.insert( + global_name.to_owned(), + Field { + field_kind: FieldKind::Struct(class_name.to_owned()), + }, + ); + } + + fn write_class_struct(&mut self, api: &ApiDump, class_name: &str) { + let structs = &mut self.std.structs; + if structs.contains_key(class_name) { + return; + } + + structs.insert(class_name.to_owned(), BTreeMap::new()); + + let mut table = BTreeMap::new(); + table.insert( + "*".to_owned(), + Field { + field_kind: FieldKind::Struct("Instance".to_owned()), + }, + ); + + self.write_class_members(api, &mut table, class_name); + + self.std.structs.insert(class_name.to_owned(), table); + } + + fn write_class_members( + &mut self, + api: &ApiDump, + table: &mut BTreeMap, + class_name: &str, + ) { + let class = api.classes.iter().find(|c| c.name == class_name).unwrap(); + + for member in &class.members { + let (name, tags, field) = match &member { + ApiMember::Callback { name, tags } => ( + name, + tags, + Some(Field { + field_kind: FieldKind::Property(PropertyWritability::OverrideFields), + }), + ), + + ApiMember::Event { name, tags } => ( + name, + tags, + Some(Field { + field_kind: FieldKind::Struct("Event".to_owned()), + }), + ), + + ApiMember::Function { + name, + tags, + parameters, + } => ( + name, + tags, + Some(Field { + field_kind: FieldKind::Function(FunctionBehavior { + // TODO: Roblox doesn't tell us which parameters are nillable or not + // So results from these are regularly wrong + // The best solution is a manual patch for every method we *know* is nillable + // e.g. WaitForChild + // We can also let some parameters be required in the middle, and fix unused_variable to accept them + + // arguments: parameters + // .iter() + // .map(|param| Argument { + // required: if param.default.is_some() { + // Required::NotRequired + // } else { + // Required::Required(None) + // }, + // argument_type: match ¶m.parameter_type { + // ApiValueType::Class { name } => { + // ArgumentType::Display(name.to_owned()) + // } + // + // ApiValueType::DataType { value } => match value { + // ApiDataType::Content => ArgumentType::String, + // ApiDataType::Other(other) => { + // ArgumentType::Display(other.to_owned()) + // } + // }, + // + // ApiValueType::Group { value } => match value { + // ApiGroupType::Table => ArgumentType::Table, + // ApiGroupType::Tuple => ArgumentType::Vararg, + // ApiGroupType::Variant => ArgumentType::Any, + // }, + // + // ApiValueType::Primitive { value } => match value { + // ApiPrimitiveType::Bool => ArgumentType::Bool, + // ApiPrimitiveType::Double + // | ApiPrimitiveType::Float + // | ApiPrimitiveType::Int + // | ApiPrimitiveType::Int64 => ArgumentType::Number, + // ApiPrimitiveType::String => ArgumentType::String, + // }, + // + // ApiValueType::Other { name } => { + // ArgumentType::Display(name.to_owned()) + // } + // }, + // }) + // .collect(), + arguments: parameters + .iter() + .map(|_| Argument { + argument_type: ArgumentType::Any, + required: Required::NotRequired, + }) + .collect(), + method: true, + }), + }), + ), + + ApiMember::Property { + name, + tags, + security, + value_type, + } => (name, tags, { + if *security == ApiPropertySecurity::default() { + let empty = Vec::new(); + let tags: &Vec = match tags { + Some(tags) => tags, + None => &empty, + }; + + let default_field = Some(Field { + field_kind: FieldKind::Property( + if tags.contains(&"ReadOnly".to_string()) { + PropertyWritability::ReadOnly + } else { + PropertyWritability::OverrideFields + }, + ), + }); + + match &value_type { + ApiValueType::Class { name } => { + self.write_class_struct(api, name); + Some(Field { + field_kind: FieldKind::Struct(name.to_owned()), + }) + } + + ApiValueType::DataType { value } => { + // See comment on `has_custom_methods` for why we're taking + // such a lax approach here. + if value.has_custom_methods() { + Some(Field { + field_kind: FieldKind::Any, + }) + } else { + default_field + } + } + + _ => default_field, + } + } else { + None + } + }), + }; + + let empty = Vec::new(); + let tags: &Vec = match tags { + Some(tags) => tags, + None => &empty, + }; + + if !self.show_deprecated && tags.contains(&"Deprecated".to_owned()) { + continue; + } + + if let Some(field) = field { + table.insert(name.to_owned(), field); + } + } + + if class.superclass != "<<>>" { + self.write_class_members(api, table, &class.superclass); + } + } + + fn write_enums(&mut self, api: &ApiDump) { + for enuhm in &api.enums { + self.std.globals.insert( + format!("Enum.{}", enuhm.name), + Field { + field_kind: FieldKind::Struct("Enum".to_owned()), + }, + ); + + for item in &enuhm.items { + self.std.globals.insert( + format!("Enum.{}.{}", enuhm.name, item.name), + Field { + field_kind: FieldKind::Struct("EnumItem".to_owned()), + }, + ); + } + } + } + + fn write_instance_new(&mut self, api: &ApiDump) { + let instance_names = api + .classes + .iter() + .filter_map(|class| { + if !class.tags.contains(&"NotCreatable".to_owned()) { + Some(class.name.to_owned()) + } else { + None + } + }) + .collect(); + + self.std.globals.insert( + "Instance.new".to_owned(), + Field { + field_kind: FieldKind::Function(FunctionBehavior { + arguments: vec![Argument { + argument_type: ArgumentType::Constant(instance_names), + required: Required::Required(None), + }], + method: false, + }), + }, + ); + } + + fn write_get_service(&mut self, api: &ApiDump) { + let service_names = api + .classes + .iter() + .filter_map(|class| { + if class.tags.contains(&"Service".to_owned()) { + Some(class.name.to_owned()) + } else { + None + } + }) + .collect(); + + let data_model = self.std.structs.get_mut("DataModel").unwrap(); + + *data_model.get_mut("GetService").unwrap() = Field { + field_kind: FieldKind::Function(FunctionBehavior { + arguments: vec![Argument { + argument_type: ArgumentType::Constant(service_names), + required: Required::Required(None), + }], + method: true, + }), + }; + } + + fn deprecated_event_methods(&mut self) { + if !self.show_deprecated { + return; + } + + let structs = &mut self.std.structs; + let event_struct = structs.get_mut("Event").unwrap(); + let (connect, wait) = ( + event_struct["Connect"].clone(), + event_struct["Wait"].clone(), + ); + + event_struct.insert("connect".to_owned(), connect); + event_struct.insert("wait".to_owned(), wait); + } +} diff --git a/selene/src/roblox/mod.rs b/selene/src/roblox/mod.rs new file mode 100644 index 00000000..c4cb5d9d --- /dev/null +++ b/selene/src/roblox/mod.rs @@ -0,0 +1,6 @@ +mod api; +mod collect_std; +mod generate_std; + +pub use collect_std::{collect_roblox_standard_library, update_roblox_std}; +pub use generate_std::RobloxGenerator; diff --git a/selene/src/standard_library.rs b/selene/src/standard_library.rs new file mode 100644 index 00000000..29e35b46 --- /dev/null +++ b/selene/src/standard_library.rs @@ -0,0 +1,93 @@ +use std::{fs, path::Path}; + +use color_eyre::eyre::Context; +use selene_lib::{ + standard_library::{v1, StandardLibrary}, + CheckerConfig, +}; + +pub fn collect_standard_library( + config: &CheckerConfig, + standard_library_name: &str, + directory: &Path, +) -> color_eyre::Result> { + let mut standard_library: Option = None; + + for segment in standard_library_name.split('+') { + let segment_library = match from_name(config, segment, directory)? { + Some(segment_library) => segment_library, + None => { + if cfg!(feature = "roblox") && segment == "roblox" { + collect_roblox_standard_library(config, directory)? + } else { + color_eyre::eyre::bail!("Could not find the standard library `{segment}`") + } + } + }; + + match standard_library.as_mut() { + Some(standard_library) => { + standard_library.extend(segment_library); + } + + None => { + standard_library = Some(segment_library); + } + } + } + + Ok(standard_library) +} + +#[cfg(feature = "roblox")] +fn collect_roblox_standard_library( + config: &CheckerConfig, + directory: &Path, +) -> color_eyre::Result { + crate::roblox::collect_roblox_standard_library(config, directory) +} + +#[cfg(not(feature = "roblox"))] +fn collect_roblox_standard_library( + _config: &CheckerConfig, + _directory: &Path, +) -> color_eyre::Result { + unreachable!() +} + +fn from_name( + config: &CheckerConfig, + standard_library_name: &str, + directory: &Path, +) -> color_eyre::Result> { + let mut library: StandardLibrary; + + let toml_file = directory.join(format!("{standard_library_name}.toml")); + if toml_file.exists() { + let content = fs::read_to_string(&toml_file)?; + + let v1_library: v1::StandardLibrary = toml::from_str(&content) + .with_context(|| format!("failed to read {}", toml_file.display()))?; + + library = v1_library.into(); + } else { + let yaml_file = directory.join(format!("{standard_library_name}.yml")); + if yaml_file.exists() { + let content = fs::read_to_string(&yaml_file) + .with_context(|| format!("failed to read {}", yaml_file.display()))?; + library = serde_yaml::from_str(&content)?; + } else { + return Ok(StandardLibrary::from_name(standard_library_name)); + } + } + + if let Some(base_name) = &library.base { + if let Some(base) = collect_standard_library(config, base_name, directory) + .with_context(|| format!("failed to collect base standard library `{base_name}`"))? + { + library.extend(base); + } + } + + Ok(Some(library)) +} diff --git a/selene/src/upgrade_std.rs b/selene/src/upgrade_std.rs new file mode 100644 index 00000000..5b25c84c --- /dev/null +++ b/selene/src/upgrade_std.rs @@ -0,0 +1,22 @@ +use std::path::PathBuf; + +pub fn upgrade_std(filename: PathBuf) -> Result<(), Box> { + let output_filename = filename.with_extension("yml"); + + if output_filename.exists() { + return Err(format!( + "{} already exists, delete it before re-running", + output_filename.display() + ) + .into()); + } + + let v1_std: selene_lib::standard_library::v1::StandardLibrary = + toml::from_str(&std::fs::read_to_string(&filename)?)?; + + let modern_std: selene_lib::standard_library::StandardLibrary = v1_std.into(); + + std::fs::write(&output_filename, serde_yaml::to_string(&modern_std)?)?; + + Ok(()) +}