diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 760b6692..03c2a1a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,33 +10,33 @@ jobs: build_windows: runs-on: windows-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Build (Default features) run: | cd selene cargo build --locked --release - name: Upload selene - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-windows path: ./target/release/selene.exe build_windows_light: runs-on: windows-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Build (Lightweight) run: | cd selene cargo build --locked --release --verbose --no-default-features - name: Upload selene-light - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-light-windows path: ./target/release/selene.exe build_mac: runs-on: macos-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Install Rust run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - name: Build (Default features) @@ -45,14 +45,14 @@ jobs: cd selene cargo build --locked --release - name: Upload selene - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-macos path: ./target/release/selene build_mac_light: runs-on: macos-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Install Rust run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - name: Build (Lightweight) @@ -61,20 +61,20 @@ jobs: cd selene cargo build --locked --release --verbose --no-default-features - name: Upload selene-light - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-light-macos path: ./target/release/selene build_linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Build (Default features) run: | cd selene cargo build --locked --release - name: Upload selene - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-linux path: ./target/release/selene @@ -86,20 +86,20 @@ jobs: command: build args: --locked --release --target aarch64-unknown-linux-gnu - name: Upload selene arm64 - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-linux-aarch64 path: ./target/aarch64-unknown-linux-gnu/release/selene build_linux_light: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Build (Lightweight) run: | cd selene cargo build --locked --release --verbose --no-default-features - name: Upload selene-light - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-light-linux path: ./target/release/selene @@ -111,7 +111,7 @@ jobs: command: build args: --locked --release --verbose --no-default-features --target aarch64-unknown-linux-gnu - name: Upload selene-light arm64 - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: selene-light-linux-aarch64 path: ./target/aarch64-unknown-linux-gnu/release/selene @@ -120,7 +120,7 @@ jobs: needs: ['build_windows_light', 'build_windows', 'build_mac', 'build_mac_light', 'build_linux', 'build_linux_light'] if: contains(github.event.head_commit.message, '[release]') steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v2 with: @@ -208,7 +208,7 @@ jobs: asset_path: ./selene-light-macos.zip asset_name: selene-light-${{ env.VERSION }}-macos.zip asset_content_type: application/zip - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v1 with: diff --git a/.github/workflows/generate-roblox-std.yml b/.github/workflows/generate-roblox-std.yml index ad12177e..f9e1c1db 100644 --- a/.github/workflows/generate-roblox-std.yml +++ b/.github/workflows/generate-roblox-std.yml @@ -4,11 +4,11 @@ jobs: generate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Generate run: cargo run --bin selene --verbose -- generate-roblox-std - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: roblox path: roblox.yml \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6a62ce..8184176e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/Kampfkarren/selene/compare/0.27.1...HEAD) ### Added - Added `Path2DControlPoint.new` to the Roblox standard library +- [Adds `lua_versions` to standard library definitions](https://kampfkarren.github.io/selene/usage/std.html#lua_versions). Specifying this will only allow the syntax used by those languages. The default standard libraries now specify these, meaning that invalid syntax for that language will no longer be supported. + +### Changed +- Upgrades to [full-moon 1.0.0](https://github.com/Kampfkarren/full-moon/blob/main/CHANGELOG.md#100---2024-10-08), which should provide faster parse speeds, support for multiple parsing errors at the same time, and support for some new Luau syntax. ## [0.27.1](https://github.com/Kampfkarren/selene/releases/tag/0.27.1) - 2024-04-28 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 3b8a6f39..3fd0ec0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,18 +82,21 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "cfg_aliases", +] + [[package]] name = "bstr" version = "1.1.0" @@ -128,6 +131,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.23" @@ -206,12 +215,6 @@ dependencies = [ "tracing-error", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -234,7 +237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -261,7 +264,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.107", ] [[package]] @@ -278,20 +281,28 @@ checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn", + "syn 2.0.79", + "unicode-xid", ] [[package]] @@ -357,15 +368,14 @@ dependencies = [ [[package]] name = "full_moon" -version = "0.19.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ef4f8ad0689d3a86bb483650422d72e6f79a37fdc83ed5426cafe96b776ce1" +checksum = "f0ed413ca670d8ec6c08d8a823975c8e14f2ada4d947f96bf7654312a72e114d" dependencies = [ "bytecount", "cfg-if", "derive_more", "full_moon_derive", - "logos", "paste", "serde", "smol_str", @@ -380,7 +390,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -572,29 +582,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "logos" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax", - "syn", -] - [[package]] name = "loom" version = "0.5.6" @@ -755,7 +742,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", ] @@ -772,9 +759,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -796,14 +783,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1e2417ef905b8ad94215f8a607bd2d0f5d13d416d18dca4a530811e8a0674c" dependencies = [ "quote", - "syn", + "syn 1.0.107", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -874,15 +861,6 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustls" version = "0.21.10" @@ -991,12 +969,6 @@ dependencies = [ "toml", ] -[[package]] -name = "semver" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" - [[package]] name = "serde" version = "1.0.152" @@ -1014,7 +986,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1067,10 +1039,11 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smol_str" -version = "0.1.23" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" +checksum = "66eaf762c5af19db3108300515c8aa7a50efc90ff745f4c62288052ebf9fdd25" dependencies = [ + "borsh", "serde", ] @@ -1107,7 +1080,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1121,6 +1094,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1156,7 +1140,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1257,7 +1241,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1362,6 +1346,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.5" @@ -1454,7 +1444,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -1476,7 +1466,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index caf6734f..9a1a5699 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MPL-2.0" repository = "https://github.com/Kampfkarren/selene" [workspace.dependencies] -full_moon = "0.19.0" +full_moon = "1.0.0" toml = "0.7.2" # Do not update this without confirming profiling uses the same version of tracy-client as selene diff --git a/docs/src/usage/std.md b/docs/src/usage/std.md index 70f21a80..7a23ed99 100644 --- a/docs/src/usage/std.md +++ b/docs/src/usage/std.md @@ -9,13 +9,34 @@ For examples of the standard library format, see: ## base -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. +Used for specifying what standard library to be based off of. This supports both builtin libraries (lua51, lua52, lua53, lua54, roblox), as well as any standard libraries that can be found in the current directory. ```yaml --- # This begins a YAML file base: lua51 # We will be extending off of Lua 5.1. ``` +## lua_versions + +Used for specifying the versions of Lua you support for the purpose of supporting the syntax of those dialects. If empty, will default to 5.1. + +Supports the following options: +- `lua51` - Lua 5.1 +- `lua52` - Lua 5.2 +- `lua53` - Lua 5.3 +- `lua54` - Lua 5.4 +- `luau` - [Luau](https://luau-lang.org) +- `luajit` - LuaJIT + +Usually you only need to specify one--for example, `lua54` will give Lua 5.4 syntax and all versions prior. That means that if you specify it, it will look something like: + +```yml +lua_versions: +- luajit +``` + +If you are extending off a library that specifies it (like `lua51`, etc) then you do not need this. If you specify it while overriding a library, it will override it. + ## 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. diff --git a/selene-lib/Cargo.toml b/selene-lib/Cargo.toml index 96f8ce6e..9dce1936 100644 --- a/selene-lib/Cargo.toml +++ b/selene-lib/Cargo.toml @@ -30,6 +30,11 @@ pretty_assertions = "1.3" termcolor = "1.2" [features] -default = ["roblox"] +default = ["lua52", "lua53", "lua54", "luajit", "roblox"] force_exhaustive_checks = [] + +lua52 = ["full_moon/lua52"] +lua53 = ["full_moon/lua53"] +lua54 = ["full_moon/lua54"] +luajit = ["full_moon/luajit"] roblox = ["full_moon/roblox"] diff --git a/selene-lib/default_std/lua52.yml b/selene-lib/default_std/lua52.yml index 8fae55e8..73eee76c 100644 --- a/selene-lib/default_std/lua52.yml +++ b/selene-lib/default_std/lua52.yml @@ -1,5 +1,7 @@ --- base: lua51 +lua_versions: + - lua52 globals: bit32.arshift: args: diff --git a/selene-lib/default_std/lua53.yml b/selene-lib/default_std/lua53.yml index 528dd2cd..581d3957 100644 --- a/selene-lib/default_std/lua53.yml +++ b/selene-lib/default_std/lua53.yml @@ -1,5 +1,7 @@ --- base: lua52 +lua_versions: + - lua53 globals: math.tointeger: args: diff --git a/selene-lib/default_std/luau.yml b/selene-lib/default_std/luau.yml index 0243c110..2f22d8e9 100644 --- a/selene-lib/default_std/luau.yml +++ b/selene-lib/default_std/luau.yml @@ -3,6 +3,8 @@ # Invalid (put in selene-lib/default_std/roblox_base.yml instead): CFrame.new(), Instance.new(), task.spawn() --- base: lua51 +lua_versions: + - luau globals: bit32.arshift: args: diff --git a/selene-lib/src/ast_util/mod.rs b/selene-lib/src/ast_util/mod.rs index 98690f26..bdaa6e76 100644 --- a/selene-lib/src/ast_util/mod.rs +++ b/selene-lib/src/ast_util/mod.rs @@ -51,7 +51,7 @@ pub fn is_vararg(expression: &ast::Expression) -> bool { if_chain::if_chain! { if let ast::Expression::Symbol(token) = expression; if let tokenizer::TokenType::Symbol { - symbol: tokenizer::Symbol::Ellipse, + symbol: tokenizer::Symbol::Ellipsis, } = token.token().token_type(); then { diff --git a/selene-lib/src/ast_util/scopes.rs b/selene-lib/src/ast_util/scopes.rs index dadcd47e..a0b3f742 100644 --- a/selene-lib/src/ast_util/scopes.rs +++ b/selene-lib/src/ast_util/scopes.rs @@ -339,8 +339,8 @@ impl ScopeVisitor { self.read_expression(rhs); } - ast::Expression::Function((name, _)) => { - self.read_name(name); + ast::Expression::Function(function_box) => { + self.read_name(&function_box.0); } ast::Expression::FunctionCall(call) => { @@ -357,7 +357,7 @@ impl ScopeVisitor { ast::Expression::Symbol(symbol) => { if *symbol.token_type() == (TokenType::Symbol { - symbol: Symbol::Ellipse, + symbol: Symbol::Ellipsis, }) { self.read_name(symbol); @@ -433,7 +433,7 @@ impl ScopeVisitor { if token.token_kind() == TokenKind::Identifier || *token.token_type() == (TokenType::Symbol { - symbol: Symbol::Ellipse, + symbol: Symbol::Ellipsis, }) { self.captured_references.insert(identifier); @@ -878,7 +878,7 @@ impl Visitor for ScopeVisitor { } #[cfg(feature = "roblox")] - fn visit_compound_assignment(&mut self, compound_assignment: &ast::types::CompoundAssignment) { + fn visit_compound_assignment(&mut self, compound_assignment: &ast::luau::CompoundAssignment) { self.read_var(compound_assignment.lhs()); self.read_expression(compound_assignment.rhs()); } @@ -911,7 +911,7 @@ impl Visitor for ScopeVisitor { self.current_scope().blocked.push(Cow::Borrowed("...")); for parameter in body.parameters() { - if let ast::Parameter::Ellipse(token) | ast::Parameter::Name(token) = parameter { + if let ast::Parameter::Ellipsis(token) | ast::Parameter::Name(token) = parameter { self.define_name(token, range(token)); } } @@ -1092,8 +1092,8 @@ impl Visitor for ScopeVisitor { } #[cfg(feature = "roblox")] - fn visit_type_info(&mut self, type_info: &ast::types::TypeInfo) { - if let ast::types::TypeInfo::Module { module, .. } = type_info { + fn visit_type_info(&mut self, type_info: &ast::luau::TypeInfo) { + if let ast::luau::TypeInfo::Module { module, .. } = type_info { self.read_name(module); } } diff --git a/selene-lib/src/ast_util/visit_nodes.rs b/selene-lib/src/ast_util/visit_nodes.rs index 163365ae..d993c15e 100644 --- a/selene-lib/src/ast_util/visit_nodes.rs +++ b/selene-lib/src/ast_util/visit_nodes.rs @@ -1,7 +1,7 @@ use full_moon::{ast::*, node::Node, tokenizer::TokenReference, visitors::Visitor}; #[cfg(feature = "roblox")] -use full_moon::ast::types::*; +use full_moon::ast::luau::*; pub(crate) trait NodeVisitor { fn visit_node(&mut self, node: &dyn Node, visitor_type: VisitorType); diff --git a/selene-lib/src/lints/bad_string_escape.rs b/selene-lib/src/lints/bad_string_escape.rs index dc4284ea..c9226210 100644 --- a/selene-lib/src/lints/bad_string_escape.rs +++ b/selene-lib/src/lints/bad_string_escape.rs @@ -107,8 +107,8 @@ impl Visitor for BadStringEscapeVisitor { fn visit_expression(&mut self, node: &ast::Expression) { if_chain::if_chain! { if let ast::Expression::String(token) = node; - if let tokenizer::TokenType::StringLiteral { literal, multi_line, quote_type } = token.token_type(); - if multi_line.is_none(); + if let tokenizer::TokenType::StringLiteral { literal, quote_type, .. } = token.token_type(); + if *quote_type != tokenizer::StringLiteralQuoteType::Brackets; then { let quote_type = *quote_type; let value_start = node.range().unwrap().0.bytes(); diff --git a/selene-lib/src/lints/deprecated.rs b/selene-lib/src/lints/deprecated.rs index 262557d3..86658c38 100644 --- a/selene-lib/src/lints/deprecated.rs +++ b/selene-lib/src/lints/deprecated.rs @@ -209,15 +209,18 @@ impl Visitor for DeprecatedVisitor<'_> { let call_suffix = suffixes.pop().unwrap(); let function_args = match call_suffix { - #[cfg_attr( - feature = "force_exhaustive_checks", - deny(non_exhaustive_omitted_patterns) - )] - ast::Suffix::Call(call) => match call { - ast::Call::AnonymousCall(args) => args, - ast::Call::MethodCall(method_call) => method_call.args(), - _ => return, - }, + ast::Suffix::Call(call) => + { + #[cfg_attr( + feature = "force_exhaustive_checks", + deny(non_exhaustive_omitted_patterns) + )] + match call { + ast::Call::AnonymousCall(args) => args, + ast::Call::MethodCall(method_call) => method_call.args(), + _ => return, + } + } _ => unreachable!("function_call.call_suffix != ast::Suffix::Call"), }; diff --git a/selene-lib/src/lints/high_cyclomatic_complexity.rs b/selene-lib/src/lints/high_cyclomatic_complexity.rs index adee8e28..695fb98a 100644 --- a/selene-lib/src/lints/high_cyclomatic_complexity.rs +++ b/selene-lib/src/lints/high_cyclomatic_complexity.rs @@ -400,7 +400,8 @@ impl Visitor for HighCyclomaticComplexityVisitor { } fn visit_expression(&mut self, expression: &ast::Expression) { - if let ast::Expression::Function((_, function_body)) = expression { + if let ast::Expression::Function(function_box) = expression { + let function_body = &function_box.1; let complexity = count_block_complexity(function_body.block(), 1); if complexity > self.config.maximum_complexity { self.positions.push(( diff --git a/selene-lib/src/lints/mismatched_arg_count.rs b/selene-lib/src/lints/mismatched_arg_count.rs index a22f0c88..a7a606f5 100644 --- a/selene-lib/src/lints/mismatched_arg_count.rs +++ b/selene-lib/src/lints/mismatched_arg_count.rs @@ -107,7 +107,7 @@ impl ParameterCount { )] match parameter { ast::Parameter::Name(_) => necessary_params += 1, - ast::Parameter::Ellipse(_) => { + ast::Parameter::Ellipsis(_) => { if necessary_params == 0 { return Self::Variable; } else { @@ -331,7 +331,8 @@ impl Visitor for MapFunctionDefinitionVisitor<'_> { .zip(local_assignment.expressions()); for (name_token, expression) in assignment_expressions { - if let ast::Expression::Function((_, function_body)) = expression { + if let ast::Expression::Function(function_box) = expression { + let function_body = &function_box.1; let identifier = range(name_token); if let Some(id) = self.find_variable(identifier) { @@ -346,7 +347,8 @@ impl Visitor for MapFunctionDefinitionVisitor<'_> { let assignment_expressions = assignment.variables().iter().zip(assignment.expressions()); for (var, expression) in assignment_expressions { - if let ast::Expression::Function((_, function_body)) = expression { + if let ast::Expression::Function(function_box) = expression { + let function_body = &function_box.1; let identifier = range(var); if let Some(reference) = self.find_reference(identifier) { diff --git a/selene-lib/src/lints/standard_library.rs b/selene-lib/src/lints/standard_library.rs index 6c4942c6..703f278c 100644 --- a/selene-lib/src/lints/standard_library.rs +++ b/selene-lib/src/lints/standard_library.rs @@ -6,7 +6,7 @@ use crate::{ use std::convert::Infallible; use full_moon::{ - ast::{self, Ast}, + ast::{self, Ast, Expression}, node::Node, tokenizer::{Position, Symbol, TokenType}, visitors::Visitor, @@ -39,6 +39,17 @@ impl Lint for StandardLibraryLint { } } +fn same_type_if_equal(lhs: &Expression, rhs: &Expression) -> Option { + let lhs_type = get_argument_type(lhs); + let rhs_type = get_argument_type(rhs); + + if lhs_type == rhs_type { + lhs_type + } else { + None + } +} + // Returns the argument type of the expression if it can be constantly resolved // Otherwise, returns None // Only attempts to resolve constants @@ -57,6 +68,8 @@ fn get_argument_type(expression: &ast::Expression) -> Option ast::UnOp::Hash(_) => Some(ArgumentType::Number.into()), ast::UnOp::Minus(_) => get_argument_type(expression), ast::UnOp::Not(_) => Some(ArgumentType::Bool.into()), + #[cfg(feature = "lua53")] + ast::UnOp::Tilde(_) => get_argument_type(expression), _ => None, } } @@ -76,7 +89,7 @@ fn get_argument_type(expression: &ast::Expression) -> Option Symbol::False => Some(ArgumentType::Bool.into()), Symbol::True => Some(ArgumentType::Bool.into()), Symbol::Nil => Some(ArgumentType::Nil.into()), - Symbol::Ellipse => Some(ArgumentType::Vararg.into()), + Symbol::Ellipsis => Some(ArgumentType::Vararg.into()), ref other => { unreachable!("TokenType::Symbol was not expected ({:?})", other) } @@ -155,16 +168,17 @@ fn get_argument_type(expression: &ast::Expression) -> Option ast::BinOp::Plus(_) | ast::BinOp::Minus(_) | ast::BinOp::Star(_) - | ast::BinOp::Slash(_) => { - let lhs_type = get_argument_type(lhs); - let rhs_type = get_argument_type(rhs); - - if lhs_type == rhs_type { - lhs_type - } else { - None - } - } + | ast::BinOp::Slash(_) => same_type_if_equal(lhs, rhs), + + #[cfg(feature = "lua53")] + ast::BinOp::DoubleLessThan(_) + | ast::BinOp::DoubleGreaterThan(_) + | ast::BinOp::Ampersand(_) + | ast::BinOp::Tilde(_) + | ast::BinOp::Pipe(_) => same_type_if_equal(lhs, rhs), + + #[cfg(any(feature = "lua53", feature = "roblox"))] + ast::BinOp::DoubleSlash(_) => same_type_if_equal(lhs, rhs), ast::BinOp::Percent(_) => Some(ArgumentType::Number.into()), @@ -177,18 +191,6 @@ fn get_argument_type(expression: &ast::Expression) -> Option None } - #[cfg(feature = "roblox")] - ast::BinOp::DoubleSlash(_) => { - let lhs_type = get_argument_type(lhs); - let rhs_type = get_argument_type(rhs); - - if lhs_type == rhs_type { - lhs_type - } else { - None - } - } - _ => None, } } @@ -532,7 +534,7 @@ impl Visitor for StandardLibraryVisitor<'_> { ast::Expression::Symbol(token_ref) => { if let TokenType::Symbol { symbol } = token_ref.token().token_type() { - if symbol == &full_moon::tokenizer::Symbol::Ellipse { + if symbol == &full_moon::tokenizer::Symbol::Ellipsis { maybe_more_arguments = true; } } diff --git a/selene-lib/src/lints/unbalanced_assignments.rs b/selene-lib/src/lints/unbalanced_assignments.rs index df1db4b2..e88f046a 100644 --- a/selene-lib/src/lints/unbalanced_assignments.rs +++ b/selene-lib/src/lints/unbalanced_assignments.rs @@ -94,7 +94,7 @@ fn expression_is_ellipsis(expression: &ast::Expression) -> bool { if let ast::Expression::Symbol(symbol) = expression { return *symbol.token_type() == TokenType::Symbol { - symbol: Symbol::Ellipse, + symbol: Symbol::Ellipsis, }; } diff --git a/selene-lib/src/standard_library/lua_versions.rs b/selene-lib/src/standard_library/lua_versions.rs new file mode 100644 index 00000000..91f79a6a --- /dev/null +++ b/selene-lib/src/standard_library/lua_versions.rs @@ -0,0 +1,99 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LuaVersion { + Lua51, + Lua52, + Lua53, + Lua54, + Luau, + LuaJIT, + + Unknown(String), +} + +impl LuaVersion { + pub fn to_str(&self) -> &str { + match self { + Self::Lua51 => "lua51", + Self::Lua52 => "lua52", + Self::Lua53 => "lua53", + Self::Lua54 => "lua54", + Self::Luau => "luau", + Self::LuaJIT => "luajit", + Self::Unknown(value) => value, + } + } + + pub fn to_lua_version(&self) -> Result { + match self { + Self::Lua51 => Ok(full_moon::ast::LuaVersion::lua51()), + + #[cfg(feature = "lua52")] + Self::Lua52 => Ok(full_moon::ast::LuaVersion::lua52()), + + #[cfg(feature = "lua53")] + Self::Lua53 => Ok(full_moon::ast::LuaVersion::lua53()), + + #[cfg(feature = "lua54")] + Self::Lua54 => Ok(full_moon::ast::LuaVersion::lua54()), + + #[cfg(feature = "roblox")] + Self::Luau => Ok(full_moon::ast::LuaVersion::luau()), + + #[cfg(feature = "luajit")] + Self::LuaJIT => Ok(full_moon::ast::LuaVersion::luajit()), + + Self::Unknown(value) => Err(LuaVersionError::Unknown(value)), + + #[allow(unreachable_patterns)] + _ => Err(LuaVersionError::FeatureNotEnabled(self.to_str())), + } + } +} + +impl FromStr for LuaVersion { + type Err = (); + + fn from_str(value: &str) -> Result { + match value { + "lua51" => Ok(Self::Lua51), + "lua52" => Ok(Self::Lua52), + "lua53" => Ok(Self::Lua53), + "lua54" => Ok(Self::Lua54), + "luau" => Ok(Self::Luau), + "luajit" => Ok(Self::LuaJIT), + _ => Err(()), + } + } +} + +pub enum LuaVersionError<'a> { + FeatureNotEnabled(&'a str), + Unknown(&'a str), +} + +impl Serialize for LuaVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_str()) + } +} + +impl<'de> Deserialize<'de> for LuaVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + if let Ok(version) = Self::from_str(&value) { + Ok(version) + } else { + Ok(Self::Unknown(value)) + } + } +} diff --git a/selene-lib/src/standard_library/mod.rs b/selene-lib/src/standard_library/mod.rs index 79ff08f5..1ad9cee5 100644 --- a/selene-lib/src/standard_library/mod.rs +++ b/selene-lib/src/standard_library/mod.rs @@ -1,3 +1,4 @@ +mod lua_versions; pub mod v1; mod v1_upgrade; @@ -15,6 +16,8 @@ use serde::{ Deserialize, Serialize, }; +pub use lua_versions::*; + lazy_static::lazy_static! { static ref ANY_TABLE: BTreeMap = { let mut map = BTreeMap::new(); @@ -117,6 +120,9 @@ pub struct StandardLibrary { #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub structs: BTreeMap>, + #[serde(default)] + pub lua_versions: Vec, + /// Internal, used for the Roblox standard library #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -314,6 +320,11 @@ impl StandardLibrary { }), ); + // Intentionally not a merge, didn't seem valuable + if !other.lua_versions.is_empty() { + self.lua_versions = other.lua_versions; + } + self.globals = globals; } @@ -339,6 +350,21 @@ impl StandardLibrary { Some(std) } + + pub fn lua_version(&self) -> (full_moon::LuaVersion, Vec) { + let mut errors = Vec::new(); + + let mut lua_version = full_moon::LuaVersion::lua51(); + + for version in &self.lua_versions { + match version.to_lua_version() { + Ok(version) => lua_version |= version, + Err(error) => errors.push(error), + } + } + + (lua_version, errors) + } } macro_rules! names { diff --git a/selene/src/main.rs b/selene/src/main.rs index 610aeb8a..3e67e471 100644 --- a/selene/src/main.rs +++ b/selene/src/main.rs @@ -15,7 +15,8 @@ use codespan_reporting::{ }, term::DisplayStyle as CodespanDisplayStyle, }; -use selene_lib::{lints::Severity, *}; +use full_moon::LuaVersion; +use selene_lib::{lints::Severity, standard_library::LuaVersionError, *}; use structopt::{clap, StructOpt}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use threadpool::ThreadPool; @@ -52,6 +53,7 @@ lazy_static::lazy_static! { static LINT_ERRORS: AtomicUsize = AtomicUsize::new(0); static LINT_WARNINGS: AtomicUsize = AtomicUsize::new(0); static PARSE_ERRORS: AtomicUsize = AtomicUsize::new(0); +static STANDARD_LIBRARY_ERRORS: AtomicUsize = AtomicUsize::new(0); fn get_color() -> ColorChoice { let lock = OPTIONS.read().unwrap(); @@ -178,7 +180,12 @@ fn emit_codespan_locked( emit_codespan(&mut stdout, files, diagnostic); } -fn read(checker: &Checker, filename: &Path, mut reader: R) { +fn read( + checker: &Checker, + filename: &Path, + lua_version: LuaVersion, + mut reader: R, +) { let mut buffer = Vec::new(); if let Err(error) = reader.read_to_end(&mut buffer) { error!( @@ -202,67 +209,75 @@ fn read(checker: &Checker, filename: &Path, mut rea let ast = { profiling::scope!("full_moon::parse"); - match full_moon::parse(&contents) { + match full_moon::parse_fallible(&contents, lua_version).into_result() { 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, - ), + Err(errors) => { + for error in errors { + PARSE_ERRORS.fetch_add(1, Ordering::SeqCst); + match error { + full_moon::Error::AstError(ast_error) => { + let token = ast_error.token(); + + 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(ast_error.error_message())], + notes: Vec::new(), + }, ) - .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}") - } + } + + 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::UnexpectedToken( + character, + ) => { + format!("unexpected character {character}") + } + + full_moon::tokenizer::TokenizerErrorType::InvalidNumber => { + "invalid number".to_string() + } + + 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(), }, - 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; @@ -376,10 +391,11 @@ fn read(checker: &Checker, filename: &Path, mut rea } } -fn read_file(checker: &Checker, filename: &Path) { +fn read_file(checker: &Checker, lua_version: LuaVersion, filename: &Path) { read( checker, filename, + lua_version, match fs::File::open(filename) { Ok(file) => file, Err(error) => { @@ -595,6 +611,21 @@ fn start(mut options: opts::Options) { } }; + let (lua_version, problems) = standard_library.lua_version(); + if !problems.is_empty() { + for problem in problems { + match problem { + LuaVersionError::FeatureNotEnabled(feature) => { + error!("lua version {feature} in standard library, but feature for it is not enabled"); + } + + LuaVersionError::Unknown(version) => { + error!("unknown lua version {version} in standard library"); + } + } + } + } + let checker = Arc::new(match Checker::new(config, standard_library) { Ok(checker) => checker, Err(error) => { @@ -608,7 +639,7 @@ fn start(mut options: opts::Options) { for filename in &options.files { if filename == "-" { let checker = Arc::clone(&checker); - pool.execute(move || read(&checker, Path::new("-"), io::stdin().lock())); + pool.execute(move || read(&checker, Path::new("-"), lua_version, io::stdin().lock())); continue; } @@ -622,7 +653,7 @@ fn start(mut options: opts::Options) { continue; } - pool.execute(move || read_file(&checker, Path::new(&filename))); + pool.execute(move || read_file(&checker, lua_version, Path::new(&filename))); } else if metadata.is_dir() { for pattern in &options.pattern { let glob = match glob::glob(&format!( @@ -646,7 +677,7 @@ fn start(mut options: opts::Options) { let checker = Arc::clone(&checker); - pool.execute(move || read_file(&checker, &path)); + pool.execute(move || read_file(&checker, lua_version, &path)); } Err(error) => { @@ -678,17 +709,19 @@ fn start(mut options: opts::Options) { pool.join(); - let (parse_errors, lint_errors, lint_warnings) = ( + let (parse_errors, lint_errors, lint_warnings, standard_library_errors) = ( PARSE_ERRORS.load(Ordering::SeqCst), LINT_ERRORS.load(Ordering::SeqCst), LINT_WARNINGS.load(Ordering::SeqCst), + STANDARD_LIBRARY_ERRORS.load(Ordering::SeqCst), ); if !options.luacheck && !options.no_summary { log_total(parse_errors, lint_errors, lint_warnings).ok(); } - let error_count = parse_errors + lint_errors + lint_warnings + pool.panic_count(); + let error_count = + parse_errors + lint_errors + lint_warnings + standard_library_errors + pool.panic_count(); if error_count > 0 { let lock = OPTIONS.read().unwrap(); let opts = lock.as_ref().unwrap(); diff --git a/selene/src/roblox/api.rs b/selene/src/roblox/api.rs index ea55a880..937677b3 100644 --- a/selene/src/roblox/api.rs +++ b/selene/src/roblox/api.rs @@ -65,20 +65,13 @@ pub enum ApiMember { } #[derive(Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct ApiParameter { - pub default: Option, - #[serde(rename = "Type")] - pub parameter_type: ApiValueType, -} +pub struct ApiParameter {} #[derive(Debug)] pub enum ApiValueType { Class { name: String }, DataType { value: ApiDataType }, - Group { value: ApiGroupType }, - Primitive { value: ApiPrimitiveType }, - Other { name: String }, + Other, } impl<'de> Deserialize<'de> for ApiValueType { @@ -118,15 +111,7 @@ impl<'de> Visitor<'de> for ApiValueTypeVisitor { value: ApiDataType::deserialize(name.into_deserializer())?, }, - "Group" => ApiValueType::Group { - value: ApiGroupType::deserialize(name.into_deserializer())?, - }, - - "Primitive" => ApiValueType::Primitive { - value: ApiPrimitiveType::deserialize(name.into_deserializer())?, - }, - - _ => ApiValueType::Other { name }, + _ => ApiValueType::Other, }) } } @@ -167,7 +152,7 @@ pub enum ApiDataType { UDim, UDim2, - Other(String), + Other, } impl ApiDataType { @@ -198,7 +183,7 @@ impl<'de> Deserialize<'de> for ApiDataType { "UDim2" => ApiDataType::UDim2, "Vector2" => ApiDataType::Vector2, "Vector3" => ApiDataType::Vector3, - _ => ApiDataType::Other(string), + _ => ApiDataType::Other, }) } } diff --git a/selene/src/standard_library.rs b/selene/src/standard_library.rs index 3c455cf2..e91726b8 100644 --- a/selene/src/standard_library.rs +++ b/selene/src/standard_library.rs @@ -12,7 +12,6 @@ use selene_lib::{ #[derive(Debug)] pub enum StandardLibraryError { BaseStd { - source: Box, name: String, }, @@ -41,7 +40,7 @@ pub enum StandardLibraryError { impl Display for StandardLibraryError { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - StandardLibraryError::BaseStd { name, .. } => { + StandardLibraryError::BaseStd { name } => { write!( formatter, "failed to collect base standard library `{name}`", @@ -200,8 +199,7 @@ fn from_name( if let Some(base_name) = &library.base { if let Some(base) = collect_standard_library(config, base_name, directory, config_directory) - .map_err(|error| StandardLibraryError::BaseStd { - source: Box::new(error), + .map_err(|_| StandardLibraryError::BaseStd { name: base_name.clone(), })? { diff --git a/selene/tests/validate_config/invalid_std_yaml/rich_output.txt b/selene/tests/validate_config/invalid_std_yaml/rich_output.txt index 571e3e62..abe87130 100644 --- a/selene/tests/validate_config/invalid_std_yaml/rich_output.txt +++ b/selene/tests/validate_config/invalid_std_yaml/rich_output.txt @@ -1,4 +1,4 @@ -error: failed to parse yml file `./tests/validate_config/invalid_std_yaml/std.yml`: unknown field `bad`, expected one of `base`, `name`, `globals`, `structs`, `last_updated`, `last_selene_version`, `roblox_classes` +error: failed to parse yml file `./tests/validate_config/invalid_std_yaml/std.yml`: unknown field `bad`, expected one of `base`, `name`, `globals`, `structs`, `lua_versions`, `last_updated`, `last_selene_version`, `roblox_classes` ┌─ std.yml:1:1 │ 1 │ bad: true