From cc645b12cf99b28f5ee87cffd39e8424d8209ae4 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 6 Aug 2024 09:21:42 +0200 Subject: [PATCH] [red-knot] Add basic WASM API (#12654) --- .github/workflows/ci.yaml | 8 +- Cargo.lock | 90 +++++-- Cargo.toml | 2 +- crates/red_knot_module_resolver/Cargo.toml | 2 +- crates/red_knot_module_resolver/build.rs | 15 +- crates/red_knot_wasm/Cargo.toml | 38 +++ crates/red_knot_wasm/src/lib.rs | 284 +++++++++++++++++++++ crates/red_knot_wasm/tests/api.rs | 21 ++ crates/ruff_db/Cargo.toml | 8 +- crates/ruff_db/src/program.rs | 2 +- crates/ruff_db/src/system/memory_fs.rs | 34 ++- 11 files changed, 473 insertions(+), 31 deletions(-) create mode 100644 crates/red_knot_wasm/Cargo.toml create mode 100644 crates/red_knot_wasm/src/lib.rs create mode 100644 crates/red_knot_wasm/tests/api.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e6f1d465881928..f3ad87ed98de0c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -111,7 +111,7 @@ jobs: - name: "Clippy" run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings - name: "Clippy (wasm)" - run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings + run: cargo clippy -p ruff_wasm -p red_knot_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings cargo-test-linux: name: "cargo test (linux)" @@ -191,10 +191,14 @@ jobs: cache-dependency-path: playground/package-lock.json - uses: jetli/wasm-pack-action@v0.4.0 - uses: Swatinem/rust-cache@v2 - - name: "Run wasm-pack" + - name: "Test ruff_wasm" run: | cd crates/ruff_wasm wasm-pack test --node + - name: "Test red_knot_wasm" + run: | + cd crates/red_knot_wasm + wasm-pack test --node cargo-build-release: name: "cargo build (release)" diff --git a/Cargo.lock b/Cargo.lock index 993a50c97f0a1a..45631fba63327e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "getrandom", "once_cell", "version_check", @@ -270,6 +270,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -459,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", - "cfg-if", + "cfg-if 1.0.0", "itoa", "rustversion", "ryu", @@ -486,7 +492,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen", ] @@ -523,7 +529,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -673,7 +679,7 @@ version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "hashbrown", "lock_api", "once_cell", @@ -686,7 +692,7 @@ version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "hashbrown", "lock_api", @@ -810,7 +816,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "home", "windows-sys 0.48.0", ] @@ -836,7 +842,7 @@ version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "windows-sys 0.52.0", @@ -900,7 +906,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi", @@ -932,7 +938,7 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crunchy", ] @@ -1141,7 +1147,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1390,6 +1396,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "mimalloc" version = "0.1.43" @@ -1448,7 +1460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", - "cfg-if", + "cfg-if 1.0.0", "cfg_aliases", "libc", ] @@ -1574,7 +1586,7 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", @@ -1928,6 +1940,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "red_knot_wasm" +version = "0.0.0" +dependencies = [ + "console_error_panic_hook", + "console_log", + "js-sys", + "log", + "red_knot_workspace", + "ruff_db", + "ruff_notebook", + "wasm-bindgen", + "wasm-bindgen-test", + "wee_alloc", +] + [[package]] name = "red_knot_workspace" version = "0.0.0" @@ -2016,7 +2044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.0", "getrandom", "libc", "spin", @@ -2134,6 +2162,7 @@ dependencies = [ "salsa", "tempfile", "tracing", + "web-time", "zip", ] @@ -2989,7 +3018,7 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "once_cell", "rustix", @@ -3034,7 +3063,7 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "proc-macro2", "quote", "syn", @@ -3078,7 +3107,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -3480,7 +3509,7 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -3505,7 +3534,7 @@ version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", @@ -3575,6 +3604,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.1" @@ -3584,6 +3623,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + [[package]] name = "which" version = "6.0.1" @@ -3858,6 +3909,7 @@ dependencies = [ "byteorder", "crc32fast", "crossbeam-utils", + "flate2", "zstd", ] diff --git a/Cargo.toml b/Cargo.toml index 90962228dfd675..d75080ab4bc056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,7 +152,7 @@ walkdir = { version = "2.3.2" } wasm-bindgen = { version = "0.2.92" } wasm-bindgen-test = { version = "0.3.42" } wild = { version = "2" } -zip = { version = "0.6.6", default-features = false, features = ["zstd"] } +zip = { version = "0.6.6", default-features = false } [workspace.lints.rust] unsafe_code = "warn" diff --git a/crates/red_knot_module_resolver/Cargo.toml b/crates/red_knot_module_resolver/Cargo.toml index 2681630e3f0511..2d88914485badc 100644 --- a/crates/red_knot_module_resolver/Cargo.toml +++ b/crates/red_knot_module_resolver/Cargo.toml @@ -25,7 +25,7 @@ zip = { workspace = true } [build-dependencies] path-slash = { workspace = true } walkdir = { workspace = true } -zip = { workspace = true } +zip = { workspace = true, features = ["zstd", "deflate"] } [dev-dependencies] ruff_db = { workspace = true, features = ["os"] } diff --git a/crates/red_knot_module_resolver/build.rs b/crates/red_knot_module_resolver/build.rs index 15f67f3bbb63ca..6e98b6714350c5 100644 --- a/crates/red_knot_module_resolver/build.rs +++ b/crates/red_knot_module_resolver/build.rs @@ -23,8 +23,21 @@ const TYPESHED_ZIP_LOCATION: &str = "/zipped_typeshed.zip"; fn zip_dir(directory_path: &str, writer: File) -> ZipResult { let mut zip = ZipWriter::new(writer); + // Use deflated compression for WASM builds because compiling `zstd-sys` requires clang + // [source](https://github.com/gyscos/zstd-rs/wiki/Compile-for-WASM) which complicates the build + // by a lot. Deflated compression is slower but it shouldn't matter much for the WASM use case + // (WASM itself is already slower than a native build for a specific platform). + // We can't use `#[cfg(...)]` here because the target-arch in a build script is the + // architecture of the system running the build script and not the architecture of the build-target. + // That's why we use the `TARGET` environment variable here. + let method = if std::env::var("TARGET").unwrap().contains("wasm32") { + CompressionMethod::Deflated + } else { + CompressionMethod::Zstd + }; + let options = FileOptions::default() - .compression_method(CompressionMethod::Zstd) + .compression_method(method) .unix_permissions(0o644); for entry in walkdir::WalkDir::new(directory_path) { diff --git a/crates/red_knot_wasm/Cargo.toml b/crates/red_knot_wasm/Cargo.toml new file mode 100644 index 00000000000000..dbcd36d5f5b9c3 --- /dev/null +++ b/crates/red_knot_wasm/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "red_knot_wasm" +version = "0.0.0" +publish = false +authors = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +description = "WebAssembly bindings for Red Knot" + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +red_knot_workspace = { workspace = true } + +ruff_db = { workspace = true } +ruff_notebook = { workspace = true } + +console_error_panic_hook = { workspace = true, optional = true } +console_log = { workspace = true } +js-sys = { workspace = true } +log = { workspace = true } +wasm-bindgen = { workspace = true } +wee_alloc = "0.4.5" + +[dev-dependencies] +wasm-bindgen-test = { workspace = true } + +[lints] +workspace = true diff --git a/crates/red_knot_wasm/src/lib.rs b/crates/red_knot_wasm/src/lib.rs new file mode 100644 index 00000000000000..1fe1b5abde0221 --- /dev/null +++ b/crates/red_knot_wasm/src/lib.rs @@ -0,0 +1,284 @@ +use std::any::Any; + +use js_sys::Error; +use wasm_bindgen::prelude::*; + +use red_knot_workspace::db::RootDatabase; +use red_knot_workspace::workspace::WorkspaceMetadata; +use ruff_db::files::{system_path_to_file, File}; +use ruff_db::program::{ProgramSettings, SearchPathSettings}; +use ruff_db::system::walk_directory::WalkDirectoryBuilder; +use ruff_db::system::{ + DirectoryEntry, MemoryFileSystem, Metadata, System, SystemPath, SystemPathBuf, + SystemVirtualPath, +}; +use ruff_notebook::Notebook; + +// Use `wee_alloc` as the global allocator. +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +#[wasm_bindgen(start)] +pub fn run() { + use log::Level; + + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); + + console_log::init_with_level(Level::Debug).expect("Initializing logger went wrong."); +} + +#[wasm_bindgen] +pub struct Workspace { + db: RootDatabase, + system: WasmSystem, +} + +#[wasm_bindgen] +impl Workspace { + #[wasm_bindgen(constructor)] + pub fn new(root: &str, settings: &Settings) -> Result { + let system = WasmSystem::new(SystemPath::new(root)); + let workspace = + WorkspaceMetadata::from_path(SystemPath::new(root), &system).map_err(into_error)?; + + let program_settings = ProgramSettings { + target_version: settings.target_version.into(), + search_paths: SearchPathSettings::default(), + }; + + let db = RootDatabase::new(workspace, program_settings, system.clone()); + + Ok(Self { db, system }) + } + + #[wasm_bindgen(js_name = "openFile")] + pub fn open_file(&mut self, path: &str, contents: &str) -> Result { + self.system + .fs + .write_file(path, contents) + .map_err(into_error)?; + + let file = system_path_to_file(&self.db, path).expect("File to exist"); + file.sync(&mut self.db); + + self.db.workspace().open_file(&mut self.db, file); + + Ok(FileHandle { + file, + path: SystemPath::new(path).to_path_buf(), + }) + } + + #[wasm_bindgen(js_name = "updateFile")] + pub fn update_file(&mut self, file_id: &FileHandle, contents: &str) -> Result<(), Error> { + if !self.system.fs.exists(&file_id.path) { + return Err(Error::new("File does not exist")); + } + + self.system + .fs + .write_file(&file_id.path, contents) + .map_err(into_error)?; + + file_id.file.sync(&mut self.db); + + Ok(()) + } + + #[wasm_bindgen(js_name = "closeFile")] + pub fn close_file(&mut self, file_id: &FileHandle) -> Result<(), Error> { + let file = file_id.file; + + self.db.workspace().close_file(&mut self.db, file); + self.system + .fs + .remove_file(&file_id.path) + .map_err(into_error)?; + + file.sync(&mut self.db); + + Ok(()) + } + + /// Checks a single file. + #[wasm_bindgen(js_name = "checkFile")] + pub fn check_file(&self, file_id: &FileHandle) -> Result, Error> { + let result = self.db.check_file(file_id.file).map_err(into_error)?; + + Ok(result.to_vec()) + } + + /// Checks all open files + pub fn check(&self) -> Result, Error> { + let result = self.db.check().map_err(into_error)?; + + Ok(result.clone()) + } + + /// Returns the parsed AST for `path` + pub fn parsed(&self, file_id: &FileHandle) -> Result { + let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file); + + Ok(format!("{:#?}", parsed.syntax())) + } + + /// Returns the token stream for `path` serialized as a string. + pub fn tokens(&self, file_id: &FileHandle) -> Result { + let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file); + + Ok(format!("{:#?}", parsed.tokens())) + } + + #[wasm_bindgen(js_name = "sourceText")] + pub fn source_text(&self, file_id: &FileHandle) -> Result { + let source_text = ruff_db::source::source_text(&self.db, file_id.file); + + Ok(source_text.to_string()) + } +} + +pub(crate) fn into_error(err: E) -> Error { + Error::new(&err.to_string()) +} + +#[derive(Debug, Eq, PartialEq)] +#[wasm_bindgen(inspectable)] +pub struct FileHandle { + path: SystemPathBuf, + file: File, +} + +#[wasm_bindgen] +impl FileHandle { + #[wasm_bindgen(js_name = toString)] + pub fn js_to_string(&self) -> String { + format!("file(id: {:?}, path: {})", self.file, self.path) + } +} + +#[wasm_bindgen] +pub struct Settings { + pub target_version: TargetVersion, +} +#[wasm_bindgen] +impl Settings { + #[wasm_bindgen(constructor)] + pub fn new(target_version: TargetVersion) -> Self { + Self { target_version } + } +} + +#[wasm_bindgen] +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum TargetVersion { + Py37, + #[default] + Py38, + Py39, + Py310, + Py311, + Py312, + Py313, +} + +impl From for ruff_db::program::TargetVersion { + fn from(value: TargetVersion) -> Self { + match value { + TargetVersion::Py37 => Self::Py37, + TargetVersion::Py38 => Self::Py38, + TargetVersion::Py39 => Self::Py39, + TargetVersion::Py310 => Self::Py310, + TargetVersion::Py311 => Self::Py311, + TargetVersion::Py312 => Self::Py312, + TargetVersion::Py313 => Self::Py313, + } + } +} + +#[derive(Debug, Clone)] +struct WasmSystem { + fs: MemoryFileSystem, +} + +impl WasmSystem { + fn new(root: &SystemPath) -> Self { + Self { + fs: MemoryFileSystem::with_current_directory(root), + } + } +} + +impl System for WasmSystem { + fn path_metadata(&self, path: &SystemPath) -> ruff_db::system::Result { + self.fs.metadata(path) + } + + fn canonicalize_path(&self, path: &SystemPath) -> ruff_db::system::Result { + Ok(self.fs.canonicalize(path)) + } + + fn read_to_string(&self, path: &SystemPath) -> ruff_db::system::Result { + self.fs.read_to_string(path) + } + + fn read_to_notebook( + &self, + path: &SystemPath, + ) -> Result { + let content = self.read_to_string(path)?; + Notebook::from_source_code(&content) + } + + fn virtual_path_metadata( + &self, + _path: &SystemVirtualPath, + ) -> ruff_db::system::Result { + Err(not_found()) + } + + fn read_virtual_path_to_string( + &self, + _path: &SystemVirtualPath, + ) -> ruff_db::system::Result { + Err(not_found()) + } + + fn read_virtual_path_to_notebook( + &self, + _path: &SystemVirtualPath, + ) -> Result { + Err(ruff_notebook::NotebookError::Io(not_found())) + } + + fn current_directory(&self) -> &SystemPath { + self.fs.current_directory() + } + + fn read_directory<'a>( + &'a self, + path: &SystemPath, + ) -> ruff_db::system::Result< + Box> + 'a>, + > { + Ok(Box::new(self.fs.read_directory(path)?)) + } + + fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder { + self.fs.walk_directory(path) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +fn not_found() -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::NotFound, "No such file or directory") +} diff --git a/crates/red_knot_wasm/tests/api.rs b/crates/red_knot_wasm/tests/api.rs new file mode 100644 index 00000000000000..66b418d038ab67 --- /dev/null +++ b/crates/red_knot_wasm/tests/api.rs @@ -0,0 +1,21 @@ +#![cfg(target_arch = "wasm32")] + +use wasm_bindgen_test::wasm_bindgen_test; + +use red_knot_wasm::{Settings, TargetVersion, Workspace}; + +#[wasm_bindgen_test] +fn check() { + let settings = Settings { + target_version: TargetVersion::Py312, + }; + let mut workspace = Workspace::new("/", &settings).expect("Workspace to be created"); + + let test = workspace + .open_file("test.py", "import random22\n") + .expect("File to be opened"); + + let result = workspace.check_file(&test).expect("Check to succeed"); + + assert_eq!(result, vec!["Unresolved import 'random22'"]); +} diff --git a/crates/ruff_db/Cargo.toml b/crates/ruff_db/Cargo.toml index 6d4ee3ff95c38f..1b57c09e67a169 100644 --- a/crates/ruff_db/Cargo.toml +++ b/crates/ruff_db/Cargo.toml @@ -29,7 +29,13 @@ salsa = { workspace = true } path-slash = { workspace = true } tracing = { workspace = true } rustc-hash = { workspace = true } -zip = { workspace = true } + +[target.'cfg(not(target_arch="wasm32"))'.dependencies] +zip = { workspace = true, features = ["zstd"] } + +[target.'cfg(target_arch="wasm32")'.dependencies] +web-time = { version = "1.1.0" } +zip = { workspace = true, features = ["deflate"] } [dev-dependencies] insta = { workspace = true } diff --git a/crates/ruff_db/src/program.rs b/crates/ruff_db/src/program.rs index cb81da90b0b78f..fbdf198824e9fd 100644 --- a/crates/ruff_db/src/program.rs +++ b/crates/ruff_db/src/program.rs @@ -77,7 +77,7 @@ impl std::fmt::Debug for TargetVersion { } /// Configures the search paths for module resolution. -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Default)] pub struct SearchPathSettings { /// List of user-provided paths that should take first priority in the module resolution. /// Examples in other type checkers are mypy's MYPYPATH environment variable, diff --git a/crates/ruff_db/src/system/memory_fs.rs b/crates/ruff_db/src/system/memory_fs.rs index 0194fb646d7eaa..c53b5d9be1311a 100644 --- a/crates/ruff_db/src/system/memory_fs.rs +++ b/crates/ruff_db/src/system/memory_fs.rs @@ -213,7 +213,7 @@ impl MemoryFileSystem { let file = get_or_create_file(&mut by_path, &normalized)?; file.content = content.to_string(); - file.last_modified = FileTime::now(); + file.last_modified = now(); Ok(()) } @@ -229,7 +229,7 @@ impl MemoryFileSystem { std::collections::hash_map::Entry::Vacant(entry) => { entry.insert(File { content: content.to_string(), - last_modified: FileTime::now(), + last_modified: now(), }); } std::collections::hash_map::Entry::Occupied(mut entry) => { @@ -284,7 +284,7 @@ impl MemoryFileSystem { let mut by_path = self.inner.by_path.write().unwrap(); let normalized = self.normalize_path(path.as_ref()); - get_or_create_file(&mut by_path, &normalized)?.last_modified = FileTime::now(); + get_or_create_file(&mut by_path, &normalized)?.last_modified = now(); Ok(()) } @@ -449,7 +449,7 @@ fn create_dir_all( path.push(component); let entry = paths.entry(path.clone()).or_insert_with(|| { Entry::Directory(Directory { - last_modified: FileTime::now(), + last_modified: now(), }) }); @@ -472,7 +472,7 @@ fn get_or_create_file<'a>( let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| { Entry::File(File { content: String::new(), - last_modified: FileTime::now(), + last_modified: now(), }) }); @@ -654,6 +654,30 @@ enum WalkerState { Nested { path: SystemPathBuf, depth: usize }, } +#[cfg(not(target_arch = "wasm32"))] +fn now() -> FileTime { + FileTime::now() +} + +#[cfg(target_arch = "wasm32")] +fn now() -> FileTime { + // Copied from FileTime::from_system_time() + let time = web_time::SystemTime::now(); + + time.duration_since(web_time::UNIX_EPOCH) + .map(|d| FileTime::from_unix_time(d.as_secs() as i64, d.subsec_nanos())) + .unwrap_or_else(|e| { + let until_epoch = e.duration(); + let (sec_offset, nanos) = if until_epoch.subsec_nanos() == 0 { + (0, 0) + } else { + (-1, 1_000_000_000 - until_epoch.subsec_nanos()) + }; + + FileTime::from_unix_time(-(until_epoch.as_secs() as i64) + sec_offset, nanos) + }) +} + #[cfg(test)] mod tests { use std::io::ErrorKind;