diff --git a/.cargo/config.toml b/.cargo/config.toml index cfdb589f77e..cc0742f7c19 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -24,8 +24,8 @@ bench_parser = "run -p xtask_bench --release -- --feature parser" bench_formatter = "run -p xtask_bench --release -- --feature formatter" bench_analyzer = "run -p xtask_bench --release -- --feature analyzer" coverage = "run -p xtask_coverage --profile=release-with-debug --" -rome-cli = "run -p rome_cli --release --" -rome-cli-dev = "run -p rome_cli --" +rome-cli = "run -p rome_bin --release --" +rome-cli-dev = "run -p rome_bin --" [profile.release] lto = true diff --git a/.github/workflows/release_cli.yml b/.github/workflows/release_cli.yml index bde9c6a0191..63e112a1f29 100644 --- a/.github/workflows/release_cli.yml +++ b/.github/workflows/release_cli.yml @@ -115,7 +115,7 @@ jobs: # Build the CLI binary - name: Build binaries - run: cargo build -p rome_cli --release --target ${{ matrix.target }} + run: cargo build -p rome_bin --release --target ${{ matrix.target }} env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc # Strip all debug symbols from the resulting binaries diff --git a/.github/workflows/release_lsp.yml b/.github/workflows/release_lsp.yml index 5d97a7e99f8..30b87cd25da 100644 --- a/.github/workflows/release_lsp.yml +++ b/.github/workflows/release_lsp.yml @@ -102,7 +102,7 @@ jobs: # Build the LSP binary - name: Build binaries - run: cargo build -p rome_lsp --release --target ${{ matrix.target }} + run: cargo build -p rome_bin --release --target ${{ matrix.target }} env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc # Strip all debug symbols from the resulting binaries @@ -113,13 +113,13 @@ jobs: run: | mkdir dist mkdir editors/vscode/server - cp target/${{ matrix.target }}/release/rome_lsp.exe editors/vscode/server/rome_lsp.exe + cp target/${{ matrix.target }}/release/rome.exe editors/vscode/server/rome.exe - name: Copy LSP binary if: matrix.os != 'windows-2022' run: | mkdir dist mkdir editors/vscode/server - cp target/${{ matrix.target }}/release/rome_lsp editors/vscode/server/rome_lsp + cp target/${{ matrix.target }}/release/rome editors/vscode/server/rome - name: Install Node.js uses: actions/setup-node@v3 diff --git a/Cargo.lock b/Cargo.lock index 6d79b1c4e4c..0d33db0aaec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,9 +863,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" [[package]] name = "libgit2-sys" @@ -1052,6 +1052,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -1399,6 +1408,24 @@ dependencies = [ "serde", ] +[[package]] +name = "rome_bin" +version = "0.0.0" +dependencies = [ + "anyhow", + "libc", + "mimalloc", + "rome_cli", + "rome_lsp", + "rome_service", + "tokio", + "tower-lsp", + "tracing", + "tracing-appender", + "tracing-subscriber", + "tracing-tree", +] + [[package]] name = "rome_cli" version = "0.0.0" @@ -1407,17 +1434,19 @@ dependencies = [ "hdrhistogram", "insta", "lazy_static", - "mimalloc", "parking_lot", "pico-args", "rayon", + "rome_bin", "rome_console", "rome_diagnostics", "rome_flags", "rome_formatter", "rome_fs", + "rome_lsp", "rome_service", "thiserror", + "tokio", "tracing", "tracing-subscriber", ] @@ -1524,6 +1553,7 @@ dependencies = [ "rome_rowan", "rome_text_edit", "rustc-hash", + "serde", "tests_macros", ] @@ -1630,7 +1660,6 @@ dependencies = [ "anyhow", "futures", "indexmap", - "mimalloc", "parking_lot", "rome_analyze", "rome_console", @@ -1647,8 +1676,6 @@ dependencies = [ "tower", "tower-lsp", "tracing", - "tracing-subscriber", - "tracing-tree", ] [[package]] @@ -2076,6 +2103,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" +dependencies = [ + "itoa 1.0.1", + "libc", + "num_threads", +] + [[package]] name = "timing" version = "0.2.3" @@ -2119,7 +2157,6 @@ dependencies = [ "mio", "num_cpus", "once_cell", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2236,6 +2273,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.20" diff --git a/crates/rome_bin/Cargo.toml b/crates/rome_bin/Cargo.toml new file mode 100644 index 00000000000..a42b08e2a82 --- /dev/null +++ b/crates/rome_bin/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rome_bin" +version = "0.0.0" +edition = "2021" +authors = ["Rome Tools Developers and Contributors"] +license = "MIT" +repository = "https://github.com/rome/tools" +description = "Rome's main binary distribution" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "rome" +path = "src/main.rs" + +[dependencies] +rome_cli = { path = "../rome_cli" } +rome_lsp = { path = "../rome_lsp" } +rome_service = { path = "../rome_service" } + +tokio = { version = "1.15.0", features = ["io-std", "io-util", "net", "time", "rt", "rt-multi-thread"] } +tower-lsp = { version = "0.17.0" } +anyhow = "1.0.52" + +tracing = { version = "0.1.31", default-features = false, features = ["std"] } +tracing-tree = "0.2.0" +tracing-subscriber = { version = "0.3.5", features = ["env-filter"] } +tracing-appender = "0.2" + +[target.'cfg(unix)'.dependencies] +libc = "0.2.127" +tokio = { version = "1.15.0", features = ["io-std", "io-util", "net", "time", "process", "rt", "rt-multi-thread", "macros"] } + +[target.'cfg(windows)'.dependencies] +mimalloc = "0.1.29" diff --git a/crates/rome_bin/README.md b/crates/rome_bin/README.md new file mode 100644 index 00000000000..a5ce01560e2 --- /dev/null +++ b/crates/rome_bin/README.md @@ -0,0 +1,11 @@ +# `rome_bin` + +Rome's main binary distribution, exposes the command line interface defined in +`rome_cli`, and the language server interface defined in `rome_lsp` and used by +the `rome` VSCode extension + +# Logs + +When the server is run in daemon mode, it will output logs to a file created in +a `rome-logs` directory inside the system temporary directory. The log file +will be rotated on a hourly basis. diff --git a/crates/rome_bin/src/cli.rs b/crates/rome_bin/src/cli.rs new file mode 100644 index 00000000000..bff1414d583 --- /dev/null +++ b/crates/rome_bin/src/cli.rs @@ -0,0 +1,19 @@ +use rome_cli::{setup_panic_handler, Arguments, CliSession, Termination}; +use rome_service::workspace; +use tokio::runtime::Runtime; + +use crate::service::open_transport; + +pub fn run_cli_session(args: Arguments) -> Result<(), Termination> { + setup_panic_handler(); + + // Try to open a connection to an existing Rome server socket, or create an + // in-process Workspace server instance if no daemon process is found + let runtime = Runtime::new()?; + let workspace = match open_transport(runtime)? { + Some(transport) => workspace::client(transport)?, + None => workspace::server(), + }; + + CliSession::new(&*workspace, args).run() +} diff --git a/crates/rome_bin/src/lib.rs b/crates/rome_bin/src/lib.rs new file mode 100644 index 00000000000..83bccac0d73 --- /dev/null +++ b/crates/rome_bin/src/lib.rs @@ -0,0 +1,9 @@ +#![doc = include_str!("../README.md")] + +mod cli; +mod server; +mod service; + +pub use cli::run_cli_session; +pub use server::{print_server_socket, run_server_session}; +pub use service::SocketTransport; diff --git a/crates/rome_bin/src/main.rs b/crates/rome_bin/src/main.rs new file mode 100644 index 00000000000..f39c6f76a62 --- /dev/null +++ b/crates/rome_bin/src/main.rs @@ -0,0 +1,29 @@ +//! This is the main binary of Rome. +//! +//! If you're curios about how to use it, check Rome's [website] +//! +//! [website]: https://rome.tools + +use cli::run_cli_session; +use rome_cli::{Arguments, Termination}; +use server::{print_server_socket, run_server_session}; + +mod cli; +mod server; +mod service; + +#[cfg(target_os = "windows")] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +fn main() -> Result<(), Termination> { + let mut args = Arguments::from_env(); + + if args.contains("__print_socket") { + print_server_socket() + } else if args.contains("__run_server") { + run_server_session() + } else { + run_cli_session(args) + } +} diff --git a/crates/rome_bin/src/server.rs b/crates/rome_bin/src/server.rs new file mode 100644 index 00000000000..f2d46e0f104 --- /dev/null +++ b/crates/rome_bin/src/server.rs @@ -0,0 +1,56 @@ +use std::env; + +use rome_cli::Termination; +use rome_lsp::ServerFactory; +use tokio::runtime::Runtime; +use tracing::{debug_span, Instrument}; +use tracing_subscriber::{prelude::*, registry, EnvFilter, Layer}; +use tracing_tree::HierarchicalLayer; + +use crate::service::{print_socket, run_daemon}; + +pub fn run_server_session() -> Result<(), Termination> { + setup_tracing_subscriber(); + + let rt = Runtime::new()?; + let factory = ServerFactory::default(); + let span = debug_span!("Running Server", pid = std::process::id()); + rt.block_on(run_daemon(factory).instrument(span))?; + + Ok(()) +} + +pub fn print_server_socket() -> Result<(), Termination> { + let rt = Runtime::new()?; + rt.block_on(print_socket())?; + Ok(()) +} + +/// Setup the [tracing]-based logging system for the server +/// The events received by the subscriber are filtered at the `info` level, +/// then printed using the [HierarchicalLayer] layer, and the resulting text +/// is written to log files rotated on a hourly basis (in +/// `rome-logs/server.log.yyyy-MM-dd-HH` files inside the system temporary +/// directory) +fn setup_tracing_subscriber() { + /// This filter enables: + /// - All spans and events at level info or higher + /// - All spans and events in the `rome_lsp` and `rome_js_parser` crates + const LOGGING_FILTER: &str = "info,rome_lsp=trace,rome_js_parser=trace"; + + let logs_dir = env::temp_dir().join("rome-logs"); + let file_appender = tracing_appender::rolling::hourly(logs_dir, "server.log"); + + registry() + .with( + HierarchicalLayer::default() + .with_indent_lines(true) + .with_indent_amount(2) + .with_bracketed_fields(true) + .with_targets(true) + .with_ansi(false) + .with_writer(file_appender) + .with_filter(EnvFilter::new(LOGGING_FILTER)), + ) + .init(); +} diff --git a/crates/rome_bin/src/service/mod.rs b/crates/rome_bin/src/service/mod.rs new file mode 100644 index 00000000000..baf50d41034 --- /dev/null +++ b/crates/rome_bin/src/service/mod.rs @@ -0,0 +1,220 @@ +//! Implements the OS dependent transport layer for the server protocol. This +//! uses a domain socket created in the global temporary directory on Unix +//! systems, and a named pipe on Windows. The protocol used for message frames +//! is based on the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#baseProtocol), +//! a simplified derivative of the HTTP protocol + +use std::{io, panic::RefUnwindSafe, str::FromStr}; + +use anyhow::{bail, ensure, Context, Error}; +use rome_service::{workspace::WorkspaceTransport, TransportError}; +use tokio::{ + io::{split, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader}, + runtime::Runtime, + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, +}; + +#[cfg(windows)] +mod windows; +#[cfg(windows)] +use self::windows::open_socket; +#[cfg(windows)] +pub(crate) use self::windows::{print_socket, run_daemon}; + +#[cfg(unix)] +mod unix; +#[cfg(unix)] +use self::unix::open_socket; +#[cfg(unix)] +pub(crate) use self::unix::{print_socket, run_daemon}; + +/// Tries to open a connection to a running daemon instance, returning a +/// [WorkspaceTransport] instance if the socket is currently active +pub(crate) fn open_transport(runtime: Runtime) -> io::Result> { + match runtime.block_on(open_socket()) { + Ok(Some(socket)) => Ok(Some(SocketTransport::open(runtime, socket))), + Ok(None) => Ok(None), + Err(err) => Err(err), + } +} + +/// Implementation of [WorkspaceTransport] for types implementing [AsyncRead] +/// and [AsyncWrite] +pub struct SocketTransport { + runtime: Runtime, + read_recv: UnboundedReceiver>, + write_send: UnboundedSender>, +} + +impl SocketTransport { + pub fn open(runtime: Runtime, socket: T) -> Self + where + T: AsyncRead + AsyncWrite + Send + 'static, + { + let (socket_read, mut socket_write) = split(socket); + let mut socket_read = BufReader::new(socket_read); + + let (read_send, read_recv) = mpsc::unbounded_channel(); + let (write_send, mut write_recv) = mpsc::unbounded_channel::>(); + + let read_task = async move { + loop { + let mut length = None; + let mut line = String::new(); + + loop { + match socket_read + .read_line(&mut line) + .await + .context("failed to read header line from the socket")? + { + // A read of 0 bytes means the connection was closed + 0 => { + bail!("the connection to the remote workspace was unexpectedly closed"); + } + // A read of two bytes corresponds to the "\r\n" sequence + // that indicates the end of the header section + 2 => { + if line != "\r\n" { + bail!("unexpected byte sequence received from the remote workspace, got {line:?} expected \"\\r\\n\""); + } + + break; + } + _ => { + let header: TransportHeader = line + .parse() + .context("failed to parse header from the remote workspace")?; + + match header { + TransportHeader::ContentLength(value) => { + length = Some(value); + } + TransportHeader::ContentType => {} + TransportHeader::Unknown(name) => { + eprintln!("ignoring unknown header {name:?}"); + } + } + + line.clear(); + } + } + } + + let length = length.context("incoming response from the remote workspace is missing the Content-Length header")?; + + let mut result = vec![0u8; length]; + socket_read.read_exact(&mut result).await.with_context(|| { + format!("failed to read message of {length} bytes from the socket") + })?; + + // Send the received message over the transport channel, or + // exit the task if the channel was closed + if read_send.send(result).is_err() { + break; + } + } + + Ok(()) + }; + + let write_task = async move { + while let Some(message) = write_recv.recv().await { + socket_write.write_all(b"Content-Length: ").await?; + + let length = message.len().to_string(); + socket_write.write_all(length.as_bytes()).await?; + socket_write.write_all(b"\r\n").await?; + + socket_write + .write_all(b"Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n") + .await?; + + socket_write.write_all(b"\r\n").await?; + + socket_write.write_all(&message).await?; + } + + Ok::<(), Error>(()) + }; + + runtime.spawn(async move { + if let Err(err) = read_task.await { + eprintln!( + "{:?}", + err.context("remote connection read task exited with an error") + ); + } + }); + + runtime.spawn(async move { + if let Err(err) = write_task.await { + eprintln!( + "{:?}", + err.context("remote connection write task exited with an error") + ); + } + }); + + Self { + runtime, + read_recv, + write_send, + } + } +} + +// Allow the socket to be recovered across panic boundaries +impl RefUnwindSafe for SocketTransport {} + +impl WorkspaceTransport for SocketTransport { + fn send(&mut self, request: Vec) -> Result<(), TransportError> { + self.write_send + .send(request) + .map_err(|_| TransportError::ChannelClosed) + } + + fn receive(&mut self) -> Result, TransportError> { + let read_recv = &mut self.read_recv; + self.runtime + .block_on(async move { read_recv.recv().await.ok_or(TransportError::ChannelClosed) }) + } +} + +enum TransportHeader { + ContentLength(usize), + ContentType, + Unknown(String), +} + +impl FromStr for TransportHeader { + type Err = Error; + + fn from_str(line: &str) -> Result { + let colon = line + .find(':') + .with_context(|| format!("could not find colon token in {line:?}"))?; + + let (name, value) = line.split_at(colon); + let value = value[1..].trim(); + + match name { + "Content-Length" => { + let value = value.parse().with_context(|| { + format!("could not parse Content-Length header value {value:?}") + })?; + + Ok(TransportHeader::ContentLength(value)) + } + "Content-Type" => { + ensure!( + value.starts_with( "application/vscode-jsonrpc"), + "invalid value for Content-Type expected \"application/vscode-jsonrpc\", got {value:?}" + ); + + Ok(TransportHeader::ContentType) + } + _ => Ok(TransportHeader::Unknown(name.into())), + } + } +} diff --git a/crates/rome_bin/src/service/unix.rs b/crates/rome_bin/src/service/unix.rs new file mode 100644 index 00000000000..2fd52534524 --- /dev/null +++ b/crates/rome_bin/src/service/unix.rs @@ -0,0 +1,127 @@ +use std::{ + convert::Infallible, + env, + io::{self, ErrorKind}, + path::PathBuf, + time::Duration, +}; + +use rome_lsp::{ServerConnection, ServerFactory}; +use tokio::{ + io::{split, Interest}, + net::{UnixListener, UnixStream}, + process::{Child, Command}, + time, +}; + +/// Returns the filesystem path of the global socket used to communicate with +/// the server daemon +fn get_socket_name() -> PathBuf { + env::temp_dir().join("rome-socket") +} + +/// Try to connect to the global socket and wait for the connection to become ready +async fn try_connect() -> io::Result { + let stream = UnixStream::connect(get_socket_name()).await?; + stream + .ready(Interest::READABLE | Interest::WRITABLE) + .await?; + Ok(stream) +} + +/// Spawn the daemon server process in the background +fn spawn_daemon() -> io::Result { + let binary = env::current_exe()?; + + let mut cmd = Command::new(binary); + cmd.arg("__run_server"); + + // Create a new session for the process and make it the leader, this will + // ensures that the child process is fully detached from its parent and will + // continue running in the background even after the parent process exits + // + // SAFETY: This closure runs in the forked child process before it starts + // executing, this is a highly unsafe environment because the process isn't + // running yet so seemingly innocuous operation like allocating memory may + // hang indefinitely. + // The only thing we do here is issuing a syscall, which is safe to do in + // this state but still "unsafe" in Rust semantics because it's technically + // mutating the shared global state of the process + unsafe { + cmd.pre_exec(|| { + libc::setsid(); + Ok(()) + }); + } + + let child = cmd.spawn()?; + Ok(child) +} + +/// Open a connection to the daemon server process, returning [None] if the +/// server is not running +pub(crate) async fn open_socket() -> io::Result> { + match try_connect().await { + Ok(socket) => Ok(Some(socket)), + Err(err) if err.kind() == ErrorKind::NotFound => Ok(None), + Err(err) => Err(err), + } +} + +/// Ensure the server daemon is running and ready to receive connections and +/// print the global socket name in the standard output +pub(crate) async fn print_socket() -> io::Result<()> { + let mut current_child: Option = None; + + loop { + // Try to open a connection on the global socket + match open_socket().await { + // The connection is open and ready => exit to printing the socket name + Ok(Some(_)) => break, + // There's no process listening on the global socket + Ok(None) => { + if let Some(current_child) = &mut current_child { + // If we have a handle to the daemon process, wait for a few + // milliseconds for it to exit, or retry the connection + tokio::select! { + result = current_child.wait() => { + let _status = result?; + return Err(io::Error::new( + io::ErrorKind::ConnectionReset, + "the server process exited before the connection could be etablished", + )); + } + _ = time::sleep(Duration::from_millis(50)) => {} + } + } else { + // Spawn the daemon process and wait a few milliseconds for + // it to become ready then retry the connection + current_child = Some(spawn_daemon()?); + time::sleep(Duration::from_millis(50)).await; + } + } + Err(err) => return Err(err), + } + } + + println!("{}", get_socket_name().display()); + Ok(()) +} + +/// Start listening on the global socket and accepting connections with the +/// provided [ServerFactory] +pub(crate) async fn run_daemon(factory: ServerFactory) -> io::Result { + let listener = UnixListener::bind(get_socket_name())?; + + loop { + let (stream, _) = listener.accept().await?; + let connection = factory.create(); + tokio::spawn(run_server(connection, stream)); + } +} + +/// Async task driving a single client connection +async fn run_server(connection: ServerConnection, stream: UnixStream) { + let (read, write) = split(stream); + connection.accept(read, write).await; +} diff --git a/crates/rome_bin/src/service/windows.rs b/crates/rome_bin/src/service/windows.rs new file mode 100644 index 00000000000..2791435fac9 --- /dev/null +++ b/crates/rome_bin/src/service/windows.rs @@ -0,0 +1,107 @@ +use std::{ + convert::Infallible, + env, + io::{self, ErrorKind}, + mem::swap, + os::windows::process::CommandExt, + process::Command, + time::Duration, +}; + +use rome_lsp::{ServerConnection, ServerFactory}; +use tokio::{ + io::split, + net::windows::named_pipe::{ClientOptions, NamedPipeClient, NamedPipeServer, ServerOptions}, + time, +}; + +/// Name of the global named pipe used to communicate with the server daemon +const PIPE_NAME: &str = r"\\.\pipe\rome-service"; + +/// Error code from the Win32 API +const ERROR_PIPE_BUSY: i32 = 231; + +/// Try to connect to the global pipe and wait for the connection to become ready +async fn try_connect() -> io::Result { + loop { + match ClientOptions::new().open(PIPE_NAME) { + Ok(client) => return Ok(client), + // If the connection failed with ERROR_PIPE_BUSY, wait a few + // milliseconds then retry the connection (we should be using + // WaitNamedPipe here but that's not exposed by tokio / mio) + Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY) => {} + Err(e) => return Err(e), + } + + time::sleep(Duration::from_millis(50)).await; + } +} + +/// Process creationg flag from the Win32 API, ensures the process is created +/// in its own group and will not be killed when the parent process exits +const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200; + +/// Spawn the daemon server process in the background +fn spawn_daemon() -> io::Result<()> { + let binary = env::current_exe()?; + + let mut cmd = Command::new(binary); + cmd.arg("__run_server"); + + cmd.creation_flags(CREATE_NEW_PROCESS_GROUP); + + cmd.spawn()?; + + Ok(()) +} + +/// Open a connection to the daemon server process, returning [None] if the +/// server is not running +pub(crate) async fn open_socket() -> io::Result> { + match try_connect().await { + Ok(socket) => Ok(Some(socket)), + Err(err) if err.kind() == ErrorKind::NotFound => Ok(None), + Err(err) => Err(err), + } +} + +/// Ensure the server daemon is running and ready to receive connections and +/// print the global pipe name in the standard output +pub(crate) async fn print_socket() -> io::Result<()> { + loop { + match open_socket().await { + Ok(Some(_)) => break, + Ok(None) => { + spawn_daemon()?; + time::sleep(Duration::from_millis(50)).await; + } + Err(err) => return Err(err), + } + } + + println!("{PIPE_NAME}"); + Ok(()) +} + +/// Start listening on the global pipe and accepting connections with the +/// provided [ServerFactory] +pub(crate) async fn run_daemon(factory: ServerFactory) -> io::Result { + let mut prev_server = ServerOptions::new() + .first_pipe_instance(true) + .create(PIPE_NAME)?; + + loop { + prev_server.connect().await?; + let mut next_server = ServerOptions::new().create(PIPE_NAME)?; + swap(&mut prev_server, &mut next_server); + + let connection = factory.create(); + tokio::spawn(run_server(connection, next_server)); + } +} + +/// Async task driving a single client connection +async fn run_server(connection: ServerConnection, stream: NamedPipeServer) { + let (read, write) = split(stream); + connection.accept(read, write).await; +} diff --git a/crates/rome_cli/Cargo.toml b/crates/rome_cli/Cargo.toml index ec667197563..163f684fff6 100644 --- a/crates/rome_cli/Cargo.toml +++ b/crates/rome_cli/Cargo.toml @@ -27,6 +27,6 @@ rayon = "1.5.1" [dev-dependencies] insta = "1.17.1" - -[target.'cfg(target_os = "windows")'.dependencies] -mimalloc = "0.1.29" +tokio = { version = "1.15.0", features = ["io-util"] } +rome_lsp = { path = "../rome_lsp" } +rome_bin = { path = "../rome_bin" } diff --git a/crates/rome_cli/examples/input.js b/crates/rome_cli/examples/input.js deleted file mode 100644 index e91bdf2e8f4..00000000000 --- a/crates/rome_cli/examples/input.js +++ /dev/null @@ -1,54 +0,0 @@ -tap.test( - "RecordImport.advance", (t) => { - mockFS( - (callback) => { - batch.setResults( - [fs.createReadStream(dataFile)], (err) => { - getBatches( - (err, batches) => { - checkStates(batches, ["started"]); - - RecordImport.advance( - (err) => { - t.error(err, "Error should be empty."); - - getBatches( - (err, batches) => { - checkStates(batches, ["process.completed"]); - - // Need to manually move to the next step - batch.importRecords( - (err) => { - t.error(err, "Error should be empty."); - - getBatches( - (err, batches) => { - checkStates(batches, ["import.completed"]); - - RecordImport.advance( - (err) => { - t.error(err, "Error should be empty."); - }, - ); - - t.ok(batch.getCurState().name(i18n)); - }, - ); - }, - ); - - t.ok(batch.getCurState().name(i18n)); - }, - ); - }, - ); - - t.ok(batch.getCurState().name(i18n)); - }, - ); - }, - ); - }, - ); - }, -); diff --git a/crates/rome_cli/examples/input.json b/crates/rome_cli/examples/input.json deleted file mode 100644 index a2eb9803b82..00000000000 --- a/crates/rome_cli/examples/input.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "string": "foo", - "boolean": false,"number": 15,"object": { "something": 15 }, - "e": "foo", - - - "a": false, - "d": { "something": 15 }, - - - "c": 15 -} diff --git a/crates/rome_cli/examples/run_cli.rs b/crates/rome_cli/examples/run_cli.rs deleted file mode 100644 index 6937b34bc55..00000000000 --- a/crates/rome_cli/examples/run_cli.rs +++ /dev/null @@ -1,29 +0,0 @@ -use rome_cli::{run_cli, CliSession, Termination}; - -/// -/// To run this example, run: -/// -/// ```bash -/// cargo run --example run_cli -/// ``` -/// Add arguments like: -/// -/// ```bash -/// cargo run --example run_cli -- --help -/// cargo run --example run_cli format -/// ``` -/// -/// To run a valid example: -/// -/// ```bash -/// cargo run --example run_cli format examples/input.json -/// ``` -/// -/// or -/// -/// ```bash -/// cargo run --example run_cli format examples/input.js -/// ``` -fn main() -> Result<(), Termination> { - run_cli(CliSession::from_env()) -} diff --git a/crates/rome_cli/src/bin/rome.rs b/crates/rome_cli/src/bin/rome.rs deleted file mode 100644 index 1bf60a5fc76..00000000000 --- a/crates/rome_cli/src/bin/rome.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! This is the main binary of Rome. -//! -//! If you're curios about how to use it, check Rome's [website] -//! -//! [website]: https://rome.tools - -use rome_cli::{run_cli, setup_panic_handler, CliSession, Termination}; - -#[cfg(target_os = "windows")] -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -/// -/// To run this example, run: -/// -/// ```bash -/// cargo run --example run_cli -/// ``` -/// Add arguments like: -/// -/// ```bash -/// cargo run --example run_cli -- --help -/// cargo run --example run_cli format -/// ``` -/// -/// To run a valid example: -/// -/// ```bash -/// cargo run --example run_cli format examples/input.json -/// ``` -fn main() -> Result<(), Termination> { - setup_panic_handler(); - run_cli(CliSession::from_env()) -} diff --git a/crates/rome_cli/src/lib.rs b/crates/rome_cli/src/lib.rs index 77e132ece5a..8da74c89593 100644 --- a/crates/rome_cli/src/lib.rs +++ b/crates/rome_cli/src/lib.rs @@ -2,9 +2,11 @@ //! to parse commands and arguments, redirect the execution of the commands and //! execute the traversal of directory and files, based on the command that were passed. -use pico_args::Arguments; +pub use pico_args::Arguments; +use rome_console::EnvConsole; use rome_flags::FeatureFlags; -use rome_service::App; +use rome_fs::OsFileSystem; +use rome_service::{App, DynRef, Workspace, WorkspaceRef}; mod commands; mod execute; @@ -25,68 +27,70 @@ pub struct CliSession<'app> { pub args: Arguments, } -impl CliSession<'static> { - pub fn from_env() -> Self { - let mut args = Arguments::from_env(); +impl<'app> CliSession<'app> { + pub fn new(workspace: &'app dyn Workspace, mut args: Arguments) -> Self { let no_colors = args.contains("--no-colors"); - Self { - app: App::from_env(no_colors), + app: App::new( + DynRef::Owned(Box::new(OsFileSystem)), + DynRef::Owned(Box::new(EnvConsole::new(no_colors))), + WorkspaceRef::Borrowed(workspace), + ), args, } } -} - -/// Main function to run Rome CLI -pub fn run_cli(mut session: CliSession) -> Result<(), Termination> { - let has_metrics = session.args.contains("--show-metrics"); - if has_metrics { - crate::metrics::init_metrics(); - } - if session.args.contains("--unstable") { - rome_flags::set_unstable_flags(FeatureFlags::ALL); - } + /// Main function to run Rome CLI + pub fn run(mut self) -> Result<(), Termination> { + let has_metrics = self.args.contains("--show-metrics"); + if has_metrics { + crate::metrics::init_metrics(); + } - let has_help = session.args.contains("--help"); - let subcommand = session - .args - .subcommand() - .map_err(|source| Termination::ParseError { - argument: "", - source, - })?; - - // True if the command line did not contain any arguments beside the subcommand - let is_empty = session.args.clone().finish().is_empty(); - - let result = match subcommand.as_deref() { - // Print the help for the subcommand if it was called with `--help` - Some(cmd) if has_help => crate::commands::help::help(session, Some(cmd)), - - Some("check") if !is_empty => crate::commands::check::check(session), - Some("ci") if !is_empty => crate::commands::ci::ci(session), - Some("format") if !is_empty => crate::commands::format::format(session), - - // Print the help for known commands called without any arguments, and exit with an error - Some(cmd @ ("check" | "ci" | "format")) => { - crate::commands::help::help(session, Some(cmd))?; - Err(Termination::EmptyArguments) + if self.args.contains("--unstable") { + rome_flags::set_unstable_flags(FeatureFlags::ALL); } - Some("init") => crate::commands::init::init(session), + let has_help = self.args.contains("--help"); + let subcommand = self + .args + .subcommand() + .map_err(|source| Termination::ParseError { + argument: "", + source, + })?; - // Print the general help if no subcommand was specified / the subcommand is `help` - None | Some("help") => crate::commands::help::help(session, None), + // True if the command line did not contain any arguments beside the subcommand + let is_empty = self.args.clone().finish().is_empty(); - Some(cmd) => Err(Termination::UnknownCommand { - command: cmd.into(), - }), - }; + let result = match subcommand.as_deref() { + // Print the help for the subcommand if it was called with `--help` + Some(cmd) if has_help => crate::commands::help::help(self, Some(cmd)), - if has_metrics { - crate::metrics::print_metrics(); - } + Some("check") if !is_empty => crate::commands::check::check(self), + Some("ci") if !is_empty => crate::commands::ci::ci(self), + Some("format") if !is_empty => crate::commands::format::format(self), - result + // Print the help for known commands called without any arguments, and exit with an error + Some(cmd @ ("check" | "ci" | "format")) => { + crate::commands::help::help(self, Some(cmd))?; + Err(Termination::EmptyArguments) + } + + Some("init") => crate::commands::init::init(self), + + // Print the general help if no subcommand was specified / the subcommand is `help` + None | Some("help") => crate::commands::help::help(self, None), + + Some(cmd) => Err(Termination::UnknownCommand { + command: cmd.into(), + }), + }; + + if has_metrics { + crate::metrics::print_metrics(); + } + + result + } } diff --git a/crates/rome_cli/src/termination.rs b/crates/rome_cli/src/termination.rs index 11cac6b964e..e8089337f1d 100644 --- a/crates/rome_cli/src/termination.rs +++ b/crates/rome_cli/src/termination.rs @@ -57,6 +57,10 @@ pub enum Termination { /// Wrapper for an underlying `rome_service` error #[error(transparent)] WorkspaceError(#[from] RomeError), + + /// Wrapper for an underlying `std::io` error + #[error(transparent)] + IoError(#[from] std::io::Error), } fn command_name() -> String { diff --git a/crates/rome_cli/tests/main.rs b/crates/rome_cli/tests/main.rs index fea935fbb53..53ded648706 100644 --- a/crates/rome_cli/tests/main.rs +++ b/crates/rome_cli/tests/main.rs @@ -8,9 +8,9 @@ use snap_test::assert_cli_snapshot; use std::{ffi::OsString, path::Path}; use pico_args::Arguments; -use rome_cli::{run_cli, CliSession, Termination}; -use rome_console::BufferConsole; -use rome_fs::MemoryFileSystem; +use rome_cli::{CliSession, Termination}; +use rome_console::{BufferConsole, Console}; +use rome_fs::{FileSystem, MemoryFileSystem}; use rome_service::{App, DynRef}; const UNFORMATTED: &str = " statement( ) "; @@ -98,13 +98,11 @@ mod check { let file_path = Path::new("check.js"); fs.insert(file_path.into(), FORMATTED.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(fs)), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Owned(Box::new(fs)), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); } @@ -116,13 +114,11 @@ mod check { let file_path = Path::new("check.js"); fs.insert(file_path.into(), PARSE_ERROR.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(fs)), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Owned(Box::new(fs)), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); match result { Err(Termination::CheckError) => {} @@ -137,13 +133,11 @@ mod check { let file_path = Path::new("check.js"); fs.insert(file_path.into(), LINT_ERROR.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(fs)), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Owned(Box::new(fs)), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); match result { Err(Termination::CheckError) => {} @@ -158,13 +152,11 @@ mod check { let file_path = Path::new("check.js"); fs.insert(file_path.into(), ERRORS.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); eprintln!("{:?}", console.out_buffer); @@ -201,17 +193,15 @@ mod check { let file_path = Path::new("fix.js"); fs.insert(file_path.into(), FIX_BEFORE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply"), file_path.as_os_str().into(), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -234,17 +224,15 @@ mod check { let file_path = Path::new("fix.js"); fs.insert(file_path.into(), FIX_AFTER.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply"), file_path.as_os_str().into(), ]), - }); + ); println!("{console:#?}"); @@ -261,18 +249,16 @@ mod check { let file_path = Path::new("fix.js"); fs.insert(file_path.into(), APPLY_SUGGESTED_BEFORE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply-suggested"), OsString::from("--apply"), file_path.as_os_str().into(), ]), - }); + ); assert!(result.is_err(), "run_cli returned {result:?}"); @@ -296,17 +282,15 @@ mod check { let file_path = Path::new("fix.js"); fs.insert(file_path.into(), APPLY_SUGGESTED_BEFORE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply-suggested"), file_path.as_os_str().into(), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -332,17 +316,15 @@ mod check { let config_path = Path::new("rome.json"); fs.insert(config_path.into(), CONFIG_LINTER_DISABLED.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply"), file_path.as_os_str().into(), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -368,13 +350,11 @@ mod check { let config_path = Path::new("rome.json"); fs.insert(config_path.into(), CONFIG_LINTER_DISABLED.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -400,17 +380,15 @@ mod check { let config_path = Path::new("rome.json"); fs.insert(config_path.into(), CONFIG_LINTER_SUPPRESSED_RULE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply"), file_path.as_os_str().into(), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -439,17 +417,15 @@ mod check { CONFIG_LINTER_SUPPRESSED_GROUP.as_bytes(), ); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--apply"), file_path.as_os_str().into(), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -477,13 +453,11 @@ mod check { let file_path = Path::new("file.js"); fs.insert(file_path.into(), NO_DEBUGGER.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -517,13 +491,11 @@ mod check { let file_path = Path::new("file.js"); fs.insert(file_path.into(), NO_DEAD_CODE_ERROR.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + ); assert!(result.is_err(), "run_cli returned {result:?}"); @@ -557,16 +529,13 @@ mod ci { fs.insert(file_path.into(), FORMATTED.as_bytes()); let mut console = BufferConsole::default(); - let app = App::with_filesystem_and_console( + + let result = run_cli( DynRef::Borrowed(&mut fs), DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), ); - let result = run_cli(CliSession { - app, - args: Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), - }); - assert!(result.is_ok(), "run_cli returned {result:?}"); let mut file = fs @@ -591,13 +560,11 @@ mod ci { let file_path = Path::new("ci.js"); fs.insert(file_path.into(), UNFORMATTED.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(fs)), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Owned(Box::new(fs)), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), + ); match result { Err(Termination::CheckError) => {} @@ -612,13 +579,11 @@ mod ci { let file_path = Path::new("ci.js"); fs.insert(file_path.into(), PARSE_ERROR.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(fs)), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Owned(Box::new(fs)), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), + ); match result { Err(Termination::CheckError) => {} @@ -634,13 +599,11 @@ mod ci { fs.insert(file_path.into(), LINT_ERROR.as_bytes()); let mut console = BufferConsole::default(); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), + ); eprintln!("{:?}", console.out_buffer); @@ -667,13 +630,11 @@ mod format { let file_path = Path::new("format.js"); fs.insert(file_path.into(), UNFORMATTED.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("format"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("format"), file_path.as_os_str().into()]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -696,19 +657,15 @@ mod format { let file_path = Path::new("format.js"); fs.insert(file_path.into(), UNFORMATTED.as_bytes()); - let app = App::with_filesystem_and_console( + let result = run_cli( DynRef::Borrowed(&mut fs), DynRef::Borrowed(&mut console), - ); - - let result = run_cli(CliSession { - app, - args: Arguments::from_vec(vec![ + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--write"), file_path.as_os_str().into(), ]), - }); + ); eprintln!("{:?}", console.out_buffer); @@ -736,13 +693,11 @@ mod format { let file_path = Path::new("format.js"); fs.insert(file_path.into(), LINT_ERROR.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![OsString::from("format"), file_path.as_os_str().into()]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![OsString::from("format"), file_path.as_os_str().into()]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -769,18 +724,16 @@ mod format { #[test] fn indent_style_parse_errors() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--indent-style"), OsString::from("invalid"), OsString::from("file.js"), ]), - }); + ); match result { Err(Termination::ParseError { argument, .. }) => assert_eq!(argument, "--indent-style"), @@ -792,18 +745,16 @@ mod format { #[test] fn indent_size_parse_errors_negative() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--indent-size"), OsString::from("-1"), OsString::from("file.js"), ]), - }); + ); match result { Err(Termination::ParseError { argument, .. }) => assert_eq!(argument, "--indent-size"), @@ -815,18 +766,16 @@ mod format { #[test] fn indent_size_parse_errors_overflow() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--indent-size"), OsString::from("257"), OsString::from("file.js"), ]), - }); + ); match result { Err(Termination::ParseError { argument, .. }) => assert_eq!(argument, "--indent-size"), @@ -838,18 +787,16 @@ mod format { #[test] fn line_width_parse_errors_negative() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--line-width"), OsString::from("-1"), OsString::from("file.js"), ]), - }); + ); match result { Err(Termination::ParseError { argument, .. }) => assert_eq!(argument, "--line-width"), @@ -861,18 +808,16 @@ mod format { #[test] fn line_width_parse_errors_overflow() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--line-width"), OsString::from("321"), OsString::from("file.js"), ]), - }); + ); match result { Err(Termination::ParseError { argument, .. }) => assert_eq!(argument, "--line-width"), @@ -891,17 +836,15 @@ mod format { let file_path = Path::new("file.js"); fs.insert(file_path.into(), CUSTOM_FORMAT_BEFORE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("file.js"), OsString::from("--write"), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -925,17 +868,15 @@ mod format { let file_path = Path::new("file.js"); fs.insert(file_path.into(), CUSTOM_FORMAT_BEFORE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("file.js"), OsString::from("--write"), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -959,17 +900,15 @@ mod format { .in_buffer .push("function f() {return{}}".to_string()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--stdin-file-path"), OsString::from("mock.js"), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -992,17 +931,15 @@ mod format { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--stdin-file-path"), OsString::from("mock.js"), ]), - }); + ); assert!(result.is_err(), "run_cli returned {result:?}"); @@ -1028,17 +965,15 @@ mod format { .in_buffer .push("function f() {return{}}".to_string()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Borrowed(&mut console), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--stdin-file-path"), OsString::from("mock.js"), ]), - }); + ); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -1062,13 +997,11 @@ mod help { #[test] fn unknown_command() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("unknown"), OsString::from("--help")]), - }); + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("unknown"), OsString::from("--help")]), + ); match result { Err(Termination::UnknownCommandHelp { command }) => assert_eq!(command, "unknown"), @@ -1085,13 +1018,11 @@ mod main { #[test] fn unknown_command() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("unknown")]), - }); + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("unknown")]), + ); match result { Err(Termination::UnknownCommand { command }) => assert_eq!(command, "unknown"), @@ -1101,17 +1032,15 @@ mod main { #[test] fn unexpected_argument() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("format"), OsString::from("--unknown"), OsString::from("file.js"), ]), - }); + ); match result { Err(Termination::UnexpectedArgument { argument, .. }) => { @@ -1123,13 +1052,11 @@ mod main { #[test] fn empty_arguments() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("format")]), - }); + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("format")]), + ); match result { Err(Termination::EmptyArguments) => {} @@ -1139,13 +1066,11 @@ mod main { #[test] fn missing_argument() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("format"), OsString::from("--write")]), - }); + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("format"), OsString::from("--write")]), + ); match result { Err(Termination::MissingArgument { argument }) => assert_eq!(argument, ""), @@ -1155,16 +1080,14 @@ mod main { #[test] fn incorrect_value() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--max-diagnostics=foo"), ]), - }); + ); match result { Err(Termination::ParseError { argument, .. }) => { @@ -1176,16 +1099,14 @@ mod main { #[test] fn overflow_value() { - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Owned(Box::new(MemoryFileSystem::default())), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![ + let result = run_cli( + DynRef::Owned(Box::new(MemoryFileSystem::default())), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![ OsString::from("check"), OsString::from("--max-diagnostics=500"), ]), - }); + ); match result { Err(Termination::OverflowNumberArgument(argument, limit)) => { @@ -1198,12 +1119,12 @@ mod main { } mod init { + use super::*; use crate::configs::CONFIG_INIT_DEFAULT; use pico_args::Arguments; - use rome_cli::{run_cli, CliSession}; use rome_console::BufferConsole; use rome_fs::{FileSystemExt, MemoryFileSystem}; - use rome_service::{App, DynRef}; + use rome_service::DynRef; use std::ffi::OsString; use std::path::Path; @@ -1211,13 +1132,11 @@ mod init { fn creates_config_file() { let mut fs = MemoryFileSystem::default(); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("init")]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("init")]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); let file_path = Path::new("rome.json"); @@ -1234,15 +1153,15 @@ mod init { } mod configuration { + use super::*; use crate::configs::{ CONFIG_ALL_FIELDS, CONFIG_BAD_LINE_WIDTH, CONFIG_INCORRECT_GLOBALS, CONFIG_INCORRECT_GLOBALS_V2, CONFIG_LINTER_WRONG_RULE, }; use pico_args::Arguments; - use rome_cli::{run_cli, CliSession}; use rome_console::BufferConsole; use rome_fs::MemoryFileSystem; - use rome_service::{App, DynRef}; + use rome_service::DynRef; use std::ffi::OsString; use std::path::Path; @@ -1252,13 +1171,11 @@ mod configuration { let file_path = Path::new("rome.json"); fs.insert(file_path.into(), CONFIG_ALL_FIELDS.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("format"), OsString::from("file.js")]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("format"), OsString::from("file.js")]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); } @@ -1270,13 +1187,11 @@ mod configuration { let file_path = Path::new("rome.json"); fs.insert(file_path.into(), CONFIG_BAD_LINE_WIDTH.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("format"), OsString::from("file.js")]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("format"), OsString::from("file.js")]), + ); assert!(result.is_err(), "run_cli returned {result:?}"); @@ -1297,13 +1212,11 @@ mod configuration { let file_path = Path::new("rome.json"); fs.insert(file_path.into(), CONFIG_LINTER_WRONG_RULE.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("check"), OsString::from("file.js")]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("check"), OsString::from("file.js")]), + ); assert!(result.is_err(), "run_cli returned {result:?}"); @@ -1322,13 +1235,11 @@ mod configuration { let file_path = Path::new("rome.json"); fs.insert(file_path.into(), CONFIG_INCORRECT_GLOBALS.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("check"), OsString::from("file.js")]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("check"), OsString::from("file.js")]), + ); assert!(result.is_err(), "run_cli returned {result:?}"); @@ -1349,14 +1260,45 @@ mod configuration { let file_path = Path::new("rome.json"); fs.insert(file_path.into(), CONFIG_INCORRECT_GLOBALS_V2.as_bytes()); - let result = run_cli(CliSession { - app: App::with_filesystem_and_console( - DynRef::Borrowed(&mut fs), - DynRef::Owned(Box::new(BufferConsole::default())), - ), - args: Arguments::from_vec(vec![OsString::from("check"), OsString::from("file.js")]), - }); + let result = run_cli( + DynRef::Borrowed(&mut fs), + DynRef::Owned(Box::new(BufferConsole::default())), + Arguments::from_vec(vec![OsString::from("check"), OsString::from("file.js")]), + ); assert!(result.is_ok(), "run_cli returned {result:?}"); } } + +/// Create an [App] instance using the provided [FileSystem] and [Console] +/// instance, and using an in-process "remote" instance of the workspace +fn run_cli<'app>( + fs: DynRef<'app, dyn FileSystem>, + console: DynRef<'app, dyn Console>, + args: Arguments, +) -> Result<(), Termination> { + use rome_bin::SocketTransport; + use rome_lsp::ServerFactory; + use rome_service::{workspace, WorkspaceRef}; + use tokio::{ + io::{duplex, split}, + runtime::Runtime, + }; + + let factory = ServerFactory::default(); + let connection = factory.create(); + + let runtime = Runtime::new().expect("failed to create runtime"); + + let (client, server) = duplex(4096); + let (stdin, stdout) = split(server); + runtime.spawn(connection.accept(stdin, stdout)); + + let transport = SocketTransport::open(runtime, client); + + let workspace = workspace::client(transport).unwrap(); + let app = App::new(fs, console, WorkspaceRef::Owned(workspace)); + + let session = CliSession { app, args }; + session.run() +} diff --git a/crates/rome_console/src/lib.rs b/crates/rome_console/src/lib.rs index 881ab086bb1..f7031074463 100644 --- a/crates/rome_console/src/lib.rs +++ b/crates/rome_console/src/lib.rs @@ -85,6 +85,12 @@ impl EnvConsole { } } +impl Default for EnvConsole { + fn default() -> Self { + Self::new(false) + } +} + impl Console for EnvConsole { fn print(&mut self, level: LogLevel, args: Markup) { let mut out = match level { diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index 494f6874be9..b7c1635b5ff 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -409,6 +409,7 @@ impl Printed { pub type FormatResult = Result; #[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// Series of errors encountered during formatting pub enum FormatError { /// In case a node can't be formatted because it either misses a require child element or diff --git a/crates/rome_js_analyze/Cargo.toml b/crates/rome_js_analyze/Cargo.toml index 4b02518fe26..a398cff36db 100644 --- a/crates/rome_js_analyze/Cargo.toml +++ b/crates/rome_js_analyze/Cargo.toml @@ -17,6 +17,7 @@ rome_console = { path = "../rome_console" } rome_diagnostics = { path = "../rome_diagnostics" } roaring = "0.9.0" rustc-hash = "1.1.0" +serde = { version = "1.0.136", features = ["derive"], optional = true } [dev-dependencies] tests_macros = { path = "../tests_macros" } diff --git a/crates/rome_js_analyze/src/lib.rs b/crates/rome_js_analyze/src/lib.rs index 1c84549d221..d777428fe87 100644 --- a/crates/rome_js_analyze/src/lib.rs +++ b/crates/rome_js_analyze/src/lib.rs @@ -9,7 +9,7 @@ use rome_js_syntax::{ suppression::{parse_suppression_comment, SuppressionCategory}, JsLanguage, }; -use std::error::Error; +use std::{borrow::Cow, error::Error}; mod analyzers; mod assists; @@ -158,10 +158,11 @@ mod tests { } /// Series of errors encountered when running rules on a file -#[derive(Debug, PartialEq, Copy, Clone)] +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RuleError { /// The rule with the specified name replaced the root of the file with a node that is not a valid root for that language. - ReplacedRootWithNonRootError { rule_name: &'static str }, + ReplacedRootWithNonRootError { rule_name: Cow<'static, str> }, } impl std::fmt::Display for RuleError { diff --git a/crates/rome_js_analyze/src/utils/rename.rs b/crates/rome_js_analyze/src/utils/rename.rs index 73d20eb6249..70641d400d1 100644 --- a/crates/rome_js_analyze/src/utils/rename.rs +++ b/crates/rome_js_analyze/src/utils/rename.rs @@ -49,6 +49,7 @@ impl RenamableNode for JsAnyRenamableDeclaration { } } +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RenameError { CannotFindDeclaration, CannotBeRenamed { diff --git a/crates/rome_js_formatter/tests/spec_test.rs b/crates/rome_js_formatter/tests/spec_test.rs index 6029ad459ef..db66b6814ea 100644 --- a/crates/rome_js_formatter/tests/spec_test.rs +++ b/crates/rome_js_formatter/tests/spec_test.rs @@ -171,7 +171,7 @@ impl SnapshotContent { /// * `json/null` -> input: `tests/specs/json/null.json`, expected output: `tests/specs/json/null.json.snap` /// * `null` -> input: `tests/specs/null.json`, expected output: `tests/specs/null.json.snap` pub fn run(spec_input_file: &str, _expected_file: &str, test_directory: &str, file_type: &str) { - let app = App::from_env(false); + let app = App::default(); let file_path = &spec_input_file; let spec_input_file = Path::new(spec_input_file); diff --git a/crates/rome_lsp/Cargo.toml b/crates/rome_lsp/Cargo.toml index 27fa9be679e..fae02de12e8 100644 --- a/crates/rome_lsp/Cargo.toml +++ b/crates/rome_lsp/Cargo.toml @@ -22,16 +22,12 @@ rome_diagnostics = { path = "../rome_diagnostics" } rome_flags = { path = "../rome_flags" } rome_rowan = { path = "../rome_rowan" } rome_console = { path = "../rome_console" } -tower-lsp = { version = "0.17.0"} -tokio = { version = "1.15.0", features = ["full" ] } -tracing = { version = "0.1.31", default-features = false, features = ["std", "max_level_trace", "release_max_level_warn"] } -tracing-tree = "0.2.0" -tracing-subscriber = { version = "0.3.5", features = ["env-filter"] } +tokio = { version = "1.15.0", features = ["io-std"] } +tower-lsp = { version = "0.17.0" } +tracing = { version = "0.1.31", default-features = false, features = ["std"] } parking_lot = "0.12.0" futures = "0.3" -[target.'cfg(target_os = "windows")'.dependencies] -mimalloc = "0.1.29" - [dev-dependencies] tower = { version = "0.4.12", features = ["timeout"] } +tokio = { version = "1.15.0", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/crates/rome_lsp/src/config.rs b/crates/rome_lsp/src/config.rs index da4b35bb6e6..934ddbc834b 100644 --- a/crates/rome_lsp/src/config.rs +++ b/crates/rome_lsp/src/config.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Error, Value}; use tracing::trace; -pub const CONFIGURATION_SECTION: &str = "rome"; +pub(crate) const CONFIGURATION_SECTION: &str = "rome"; #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -27,7 +27,7 @@ impl Config { } } - pub fn set_workspace_settings(&mut self, value: Value) -> Result<(), Error> { + pub(crate) fn set_workspace_settings(&mut self, value: Value) -> Result<(), Error> { let workspace_settings = serde_json::from_value(value)?; self.settings = workspace_settings; trace!( @@ -41,7 +41,7 @@ impl Config { /// /// If the configuration file is found we use it with its defaults, otherwise /// we use the settings coming from the client - pub fn as_workspace_settings( + pub(crate) fn as_workspace_settings( &self, configuration: Option, ) -> settings::WorkspaceSettings { diff --git a/crates/rome_lsp/src/documents.rs b/crates/rome_lsp/src/documents.rs index 8f4f66ef2e3..d7c24d96357 100644 --- a/crates/rome_lsp/src/documents.rs +++ b/crates/rome_lsp/src/documents.rs @@ -6,7 +6,7 @@ use crate::line_index::LineIndex; /// /// [language identifiers]: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum EditorLanguage { +pub(crate) enum EditorLanguage { JavaScript, JavaScriptReact, TypeScript, diff --git a/crates/rome_lsp/src/handlers.rs b/crates/rome_lsp/src/handlers.rs index 2a74799084a..0a65a960c87 100644 --- a/crates/rome_lsp/src/handlers.rs +++ b/crates/rome_lsp/src/handlers.rs @@ -1,4 +1,4 @@ -pub mod analysis; -pub mod formatting; -pub mod rename; -pub mod text_document; +pub(crate) mod analysis; +pub(crate) mod formatting; +pub(crate) mod rename; +pub(crate) mod text_document; diff --git a/crates/rome_lsp/src/lib.rs b/crates/rome_lsp/src/lib.rs index 773134308ca..6f823c949eb 100644 --- a/crates/rome_lsp/src/lib.rs +++ b/crates/rome_lsp/src/lib.rs @@ -1,11 +1,13 @@ -pub mod config; -pub mod server; - mod capabilities; +mod config; mod documents; mod handlers; mod line_index; mod requests; +mod server; mod session; mod url_interner; mod utils; + +pub use crate::config::WorkspaceSettings; +pub use crate::server::{LSPServer, ServerConnection, ServerFactory}; diff --git a/crates/rome_lsp/src/main.rs b/crates/rome_lsp/src/main.rs deleted file mode 100644 index 17ad2c7f6d8..00000000000 --- a/crates/rome_lsp/src/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -use tracing::{debug_span, Instrument}; -use tracing_subscriber::{layer::SubscriberExt, prelude::*, registry, EnvFilter}; -use tracing_tree::HierarchicalLayer; - -use rome_lsp::server::run_server; - -#[cfg(target_os = "windows")] -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -/// This filter enables: -/// - All spans and events at level info or higher -/// - All spans and events in the `rome_lsp` and `rome_js_parser` crates -const LOGGING_FILTER: &str = "info,rome_lsp=trace,rome_js_parser=trace"; - -#[tokio::main] -async fn main() { - let stdin = tokio::io::stdin(); - let stdout = tokio::io::stdout(); - - registry() - .with( - HierarchicalLayer::default() - .with_indent_lines(true) - .with_indent_amount(2) - .with_bracketed_fields(true) - .with_targets(true) - .with_filter(EnvFilter::new(LOGGING_FILTER)), - ) - .init(); - - let span = debug_span!("Running LSP Server", pid = std::process::id()); - run_server(stdin, stdout).instrument(span).await -} diff --git a/crates/rome_lsp/src/requests/mod.rs b/crates/rome_lsp/src/requests/mod.rs index 17cc1bc7a22..27036516793 100644 --- a/crates/rome_lsp/src/requests/mod.rs +++ b/crates/rome_lsp/src/requests/mod.rs @@ -1 +1 @@ -pub mod syntax_tree; +pub(crate) mod syntax_tree; diff --git a/crates/rome_lsp/src/requests/syntax_tree.rs b/crates/rome_lsp/src/requests/syntax_tree.rs index 93f007862f6..66fafb072cb 100644 --- a/crates/rome_lsp/src/requests/syntax_tree.rs +++ b/crates/rome_lsp/src/requests/syntax_tree.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use tower_lsp::lsp_types::{TextDocumentIdentifier, Url}; use tracing::info; -pub const SYNTAX_TREE_REQUEST: &str = "rome/syntaxTree"; +pub const SYNTAX_TREE_REQUEST: &str = "rome_lsp/syntaxTree"; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] diff --git a/crates/rome_lsp/src/server.rs b/crates/rome_lsp/src/server.rs index ab614824441..81191b9a8bc 100644 --- a/crates/rome_lsp/src/server.rs +++ b/crates/rome_lsp/src/server.rs @@ -1,11 +1,13 @@ +use std::sync::Arc; + use crate::capabilities::server_capabilities; use crate::requests::syntax_tree::{SyntaxTreePayload, SYNTAX_TREE_REQUEST}; use crate::session::Session; use crate::utils::into_lsp_error; use crate::{handlers, requests}; -use rome_service::load_config; -use serde_json::Value; -use tokio::io::{Stdin, Stdout}; +use futures::future::ready; +use rome_service::{load_config, workspace, Workspace}; +use tokio::io::{AsyncRead, AsyncWrite}; use tower_lsp::jsonrpc::Result as LspResult; use tower_lsp::{lsp_types::*, ClientSocket}; use tower_lsp::{Client, LanguageServer, LspService, Server}; @@ -16,12 +18,13 @@ pub struct LSPServer { } impl LSPServer { - fn new(client: Client) -> Self { - let session = Session::new(client); - Self { session } + fn new(client: Client, workspace: Arc) -> Self { + Self { + session: Session::new(client, workspace), + } } - async fn syntax_tree_request(&self, params: SyntaxTreePayload) -> LspResult> { + async fn syntax_tree_request(&self, params: SyntaxTreePayload) -> LspResult { trace!( "Calling method: {}\n with params: {:?}", SYNTAX_TREE_REQUEST, @@ -29,11 +32,7 @@ impl LSPServer { ); let url = params.text_document.uri; - let result = - requests::syntax_tree::syntax_tree(&self.session, &url).map_err(into_lsp_error)?; - - let result = serde_json::to_value(result).map_err(into_lsp_error)?; - Ok(Some(result)) + requests::syntax_tree::syntax_tree(&self.session, &url).map_err(into_lsp_error) } } @@ -162,13 +161,94 @@ impl LanguageServer for LSPServer { } } -pub fn build_server() -> (LspService, ClientSocket) { - LspService::build(LSPServer::new) - .custom_method(SYNTAX_TREE_REQUEST, LSPServer::syntax_tree_request) - .finish() +/// Factory data structure responsible for creating [ServerConnection] handles +/// for each incoming connection accepted by the server +#[derive(Default)] +pub struct ServerFactory { + /// Optional [Workspace] instance shared between all clients. Currently + /// this field is always [None] (meaning each connection will get its own + /// workspace) until we figure out how to handle concurrent access to the + /// same workspace from multiple client + workspace: Option>, +} + +/// Helper method for wrapping a [Workspace] method in a `custom_method` for +/// the [LSPServer] +macro_rules! workspace_method { + ( $builder:ident, $method:ident ) => { + $builder = $builder.custom_method( + concat!("rome/", stringify!($method)), + |server: &LSPServer, params| { + ready( + server + .session + .workspace + .$method(params) + .map_err(into_lsp_error), + ) + }, + ); + }; } -pub async fn run_server(stdin: Stdin, stdout: Stdout) { - let (service, messages) = build_server(); - Server::new(stdin, stdout, messages).serve(service).await; +impl ServerFactory { + /// Create a new [ServerConnection] from this factory + pub fn create(&self) -> ServerConnection { + let workspace = self + .workspace + .clone() + .unwrap_or_else(workspace::server_sync); + + let mut builder = LspService::build(move |client| LSPServer::new(client, workspace)); + + builder = builder.custom_method(SYNTAX_TREE_REQUEST, LSPServer::syntax_tree_request); + + // supports_feature is special because it returns a bool instead of a Result + builder = builder.custom_method("rome/supports_feature", |server: &LSPServer, params| { + ready(Ok(server.session.workspace.supports_feature(params))) + }); + + workspace_method!(builder, update_settings); + workspace_method!(builder, open_file); + workspace_method!(builder, get_syntax_tree); + workspace_method!(builder, get_control_flow_graph); + workspace_method!(builder, get_formatter_ir); + workspace_method!(builder, change_file); + workspace_method!(builder, close_file); + workspace_method!(builder, pull_diagnostics); + workspace_method!(builder, pull_actions); + workspace_method!(builder, format_file); + workspace_method!(builder, format_range); + workspace_method!(builder, format_on_type); + workspace_method!(builder, fix_file); + workspace_method!(builder, rename); + + let (service, socket) = builder.finish(); + ServerConnection { socket, service } + } +} + +/// Handle type created by the server for each incoming connection +pub struct ServerConnection { + socket: ClientSocket, + service: LspService, +} + +impl ServerConnection { + /// Destructure a connection into its inner service instance and socket + pub fn into_inner(self) -> (LspService, ClientSocket) { + (self.service, self.socket) + } + + /// Accept an incoming connection and run the server async I/O loop to + /// completion + pub async fn accept(self, stdin: I, stdout: O) + where + I: AsyncRead + Unpin, + O: AsyncWrite, + { + Server::new(stdin, stdout, self.socket) + .serve(self.service) + .await; + } } diff --git a/crates/rome_lsp/src/session.rs b/crates/rome_lsp/src/session.rs index 952b2af76d8..2c04697d0ff 100644 --- a/crates/rome_lsp/src/session.rs +++ b/crates/rome_lsp/src/session.rs @@ -10,12 +10,12 @@ use rome_analyze::RuleCategories; use rome_diagnostics::file::FileId; use rome_fs::{FileSystem, OsFileSystem, RomePath}; use rome_service::configuration::Configuration; -use rome_service::workspace; use rome_service::workspace::UpdateSettingsParams; use rome_service::workspace::{FeatureName, PullDiagnosticsParams, SupportsFeatureParams}; use rome_service::Workspace; use rome_service::{DynRef, RomeError}; use std::collections::HashMap; +use std::sync::Arc; use tower_lsp::lsp_types; use tracing::{error, trace}; @@ -29,7 +29,7 @@ pub(crate) struct Session { /// the configuration of the LSP pub(crate) config: RwLock, - pub(crate) workspace: Box, + pub(crate) workspace: Arc, /// File system to read files inside the workspace pub(crate) fs: DynRef<'static, dyn FileSystem>, @@ -42,7 +42,7 @@ pub(crate) struct Session { } impl Session { - pub(crate) fn new(client: tower_lsp::Client) -> Self { + pub(crate) fn new(client: tower_lsp::Client, workspace: Arc) -> Self { let client_capabilities = RwLock::new(Default::default()); let documents = Default::default(); let url_interner = Default::default(); @@ -51,7 +51,7 @@ impl Session { Self { client, client_capabilities, - workspace: workspace::server(), + workspace, documents, url_interner, config, diff --git a/crates/rome_lsp/tests/server.rs b/crates/rome_lsp/tests/server.rs index 8c2ebcb6112..013909b71f1 100644 --- a/crates/rome_lsp/tests/server.rs +++ b/crates/rome_lsp/tests/server.rs @@ -6,9 +6,9 @@ use futures::Sink; use futures::SinkExt; use futures::Stream; use futures::StreamExt; -use rome_lsp::config::WorkspaceSettings; -use rome_lsp::server::build_server; -use rome_lsp::server::LSPServer; +use rome_lsp::LSPServer; +use rome_lsp::ServerFactory; +use rome_lsp::WorkspaceSettings; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json::{from_value, to_value}; @@ -234,7 +234,8 @@ where #[tokio::test] async fn basic_lifecycle() -> Result<()> { - let (service, client) = build_server(); + let factory = ServerFactory::default(); + let (service, client) = factory.create().into_inner(); let (stream, sink) = client.split(); let mut server = Server::new(service); @@ -251,7 +252,8 @@ async fn basic_lifecycle() -> Result<()> { #[tokio::test] async fn document_lifecycle() -> Result<()> { - let (service, client) = build_server(); + let factory = ServerFactory::default(); + let (service, client) = factory.create().into_inner(); let (stream, sink) = client.split(); let mut server = Server::new(service); @@ -271,7 +273,8 @@ async fn document_lifecycle() -> Result<()> { #[tokio::test] async fn pull_code_actions() -> Result<()> { - let (service, client) = build_server(); + let factory = ServerFactory::default(); + let (service, client) = factory.create().into_inner(); let (stream, sink) = client.split(); let mut server = Server::new(service); @@ -327,7 +330,8 @@ async fn pull_code_actions() -> Result<()> { #[tokio::test] async fn format_with_syntax_errors() -> Result<()> { - let (service, client) = build_server(); + let factory = ServerFactory::default(); + let (service, client) = factory.create().into_inner(); let (stream, sink) = client.split(); let mut server = Server::new(service); diff --git a/crates/rome_service/Cargo.toml b/crates/rome_service/Cargo.toml index 9353d38eb72..6de398e2a8c 100644 --- a/crates/rome_service/Cargo.toml +++ b/crates/rome_service/Cargo.toml @@ -12,32 +12,20 @@ license = "MIT" dashmap = "5.2.0" serde = { version = "1.0.133", features = ["derive"] } serde_json = { version = "1.0.74", features = ["raw_value"] } -rome_analyze = { path = "../rome_analyze" } +rome_analyze = { path = "../rome_analyze", features = ["serde"] } rome_console = { path = "../rome_console" } -rome_diagnostics = { path = "../rome_diagnostics" } -rome_formatter = { path = "../rome_formatter" } -rome_fs = { path = "../rome_fs" } -rome_js_analyze = { path = "../rome_js_analyze" } -rome_js_syntax = { path = "../rome_js_syntax" } +rome_diagnostics = { path = "../rome_diagnostics", features = ["serde"] } +rome_formatter = { path = "../rome_formatter", features = ["serde"] } +rome_fs = { path = "../rome_fs", features = ["serde"] } +rome_js_analyze = { path = "../rome_js_analyze", features = ["serde"] } +rome_js_syntax = { path = "../rome_js_syntax", features = ["serde"] } rome_js_parser = { path = "../rome_js_parser" } -rome_js_formatter = { path = "../rome_js_formatter" } +rome_js_formatter = { path = "../rome_js_formatter", features = ["serde"] } rome_js_semantic = { path = "../rome_js_semantic" } -rome_rowan = { path = "../rome_rowan" } -rome_text_edit = { path = "../rome_text_edit" } +rome_rowan = { path = "../rome_rowan", features = ["serde"] } +rome_text_edit = { path = "../rome_text_edit", features = ["serde"] } indexmap = { version = "1.9.1", features = ["serde"] } schemars = { version = "0.8.10", features = ["indexmap1"], optional = true } [features] schemars = ["dep:schemars", "rome_formatter/serde"] -serde_workspace = [ - "schemars", - "rome_analyze/serde", - "rome_diagnostics/serde", - "rome_formatter/serde", - "rome_fs/serde", - "rome_js_syntax/serde", - "rome_js_formatter/serde", - "rome_rowan/serde", - "rome_text_edit/serde" -] - diff --git a/crates/rome_service/src/configuration/mod.rs b/crates/rome_service/src/configuration/mod.rs index 7b6c5195eec..6cefc8fdafb 100644 --- a/crates/rome_service/src/configuration/mod.rs +++ b/crates/rome_service/src/configuration/mod.rs @@ -17,7 +17,6 @@ mod formatter; mod javascript; pub mod linter; -#[cfg(feature = "serde_workspace")] pub(crate) use javascript::{deserialize_globals, serialize_globals}; pub use linter::{RuleConfiguration, Rules}; @@ -63,6 +62,7 @@ impl Configuration { } /// Series of errors that can be thrown while computing the configuration +#[derive(serde::Serialize, serde::Deserialize)] pub enum ConfigurationError { /// Thrown when the program can't serialize the configuration, while saving it SerializationError, diff --git a/crates/rome_service/src/file_handlers/javascript.rs b/crates/rome_service/src/file_handlers/javascript.rs index d59f0349296..5a7d48e73d9 100644 --- a/crates/rome_service/src/file_handlers/javascript.rs +++ b/crates/rome_service/src/file_handlers/javascript.rs @@ -32,20 +32,14 @@ use rome_console::codespan::Severity; use std::borrow::Cow; use std::fmt::Debug; -#[derive(Debug, Clone, Default)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct JsFormatSettings { pub quote_style: Option, } -#[derive(Debug, Clone, Default)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct JsLinterSettings { pub globals: Vec, } @@ -338,7 +332,7 @@ fn fix_all(params: FixAllParams) -> Result { None => { return Err(RomeError::RuleError( RuleError::ReplacedRootWithNonRootError { - rule_name: action.rule_name, + rule_name: Cow::Borrowed(action.rule_name), }, )) } diff --git a/crates/rome_service/src/lib.rs b/crates/rome_service/src/lib.rs index b837544f8ad..909329b4e70 100644 --- a/crates/rome_service/src/lib.rs +++ b/crates/rome_service/src/lib.rs @@ -3,8 +3,9 @@ use rome_formatter::FormatError; use rome_fs::{FileSystem, OsFileSystem, RomePath}; use rome_js_analyze::utils::rename::RenameError; use rome_js_analyze::RuleError; +use serde::{Deserialize, Serialize}; use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; @@ -24,12 +25,13 @@ pub struct App<'app> { /// A reference to the internal virtual file system pub fs: DynRef<'app, dyn FileSystem>, /// A reference to the internal workspace - pub workspace: DynRef<'app, dyn Workspace>, + pub workspace: WorkspaceRef<'app>, /// A reference to the internal console, where its buffer will be used to write messages and /// errors pub console: DynRef<'app, dyn Console>, } +#[derive(Serialize, Deserialize)] /// Generic errors thrown during rome operations pub enum RomeError { /// The project contains uncommitted changes @@ -53,16 +55,18 @@ pub enum RomeError { Configuration(ConfigurationError), /// Error thrown when Rome cannot rename a symbol. RenameError(RenameError), + /// Error emitted by the underlying transport layer for a remote Workspace + TransportError(TransportError), } impl Debug for RomeError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) } } impl Display for RomeError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { RomeError::SourceFileNotSupported(path) => { let ext = path.extension().and_then(|ext| ext.to_str()); @@ -104,7 +108,7 @@ impl Display for RomeError { ) } - RomeError::Configuration(error) => std::fmt::Display::fmt(error, f), + RomeError::Configuration(error) => fmt::Display::fmt(error, f), RomeError::DirtyWorkspace => { write!(f, "Uncommitted changes in repository") } @@ -132,6 +136,10 @@ impl Display for RomeError { "the linter encountered an error while analyzing the file: {cause}", ) } + + RomeError::TransportError(err) => { + write!(f, "{err}",) + } } } } @@ -144,12 +152,48 @@ impl From for RomeError { } } -impl<'app> App<'app> { - /// Create a new instance of the app using the [OsFileSystem] and [EnvConsole] - pub fn from_env(no_colors: bool) -> Self { +impl From for RomeError { + fn from(err: TransportError) -> Self { + Self::TransportError(err) + } +} + +#[derive(Debug, Serialize, Deserialize)] +/// Error emitted by the underlying transport layer for a remote Workspace +pub enum TransportError { + /// Error emitted by the transport layer if the connection was lost due to an I/O error + ChannelClosed, + /// Error caused by a serialization or deserialization issue + SerdeError(String), + /// Generic error type for RPC errors that can't be deserialized into RomeError + RPCError(String), +} + +impl Error for TransportError {} + +impl Display for TransportError { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + match self { + TransportError::SerdeError(err) => write!(fmt, "serialization error: {err}"), + TransportError::ChannelClosed => fmt.write_str( + "a request to the remote workspace failed because the connection was interrupted", + ), + TransportError::RPCError(err) => fmt.write_str(err), + } + } +} + +impl From for TransportError { + fn from(err: serde_json::Error) -> Self { + TransportError::SerdeError(err.to_string()) + } +} + +impl Default for App<'static> { + fn default() -> Self { Self::with_filesystem_and_console( DynRef::Owned(Box::new(OsFileSystem)), - DynRef::Owned(Box::new(EnvConsole::new(no_colors))), + DynRef::Owned(Box::new(EnvConsole::default())), ) } } @@ -159,11 +203,36 @@ impl<'app> App<'app> { pub fn with_filesystem_and_console( fs: DynRef<'app, dyn FileSystem>, console: DynRef<'app, dyn Console>, + ) -> Self { + Self::new(fs, console, WorkspaceRef::Owned(workspace::server())) + } + + /// Create a new instance of the app using the specified [FileSystem], [Console] and [Workspace] implementation + pub fn new( + fs: DynRef<'app, dyn FileSystem>, + console: DynRef<'app, dyn Console>, + workspace: WorkspaceRef<'app>, ) -> Self { Self { fs, console, - workspace: DynRef::Owned(workspace::server()), + workspace, + } + } +} + +pub enum WorkspaceRef<'app> { + Owned(Box), + Borrowed(&'app dyn Workspace), +} + +impl<'app> Deref for WorkspaceRef<'app> { + type Target = dyn Workspace + 'app; + + fn deref(&self) -> &Self::Target { + match self { + WorkspaceRef::Owned(inner) => &**inner, + WorkspaceRef::Borrowed(inner) => *inner, } } } diff --git a/crates/rome_service/src/settings.rs b/crates/rome_service/src/settings.rs index 8cafd00a2aa..7cbcf6d7316 100644 --- a/crates/rome_service/src/settings.rs +++ b/crates/rome_service/src/settings.rs @@ -7,20 +7,17 @@ use rome_js_syntax::JsLanguage; use std::sync::{RwLock, RwLockReadGuard}; /// Global settings for the entire workspace -#[derive(Debug, Default)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct WorkspaceSettings { /// Formatter settings applied to all files in the workspaces - #[cfg_attr(feature = "serde_workspace", serde(default))] + #[serde(default)] pub format: FormatSettings, /// Linter settings applied to all files in the workspace - #[cfg_attr(feature = "serde_workspace", serde(default))] + #[serde(default)] pub linter: LinterSettings, /// Language specific settings - #[cfg_attr(feature = "serde_workspace", serde(default))] + #[serde(default)] pub languages: LanguagesSettings, } @@ -66,11 +63,8 @@ impl WorkspaceSettings { } /// Formatter settings for the entire workspace -#[derive(Debug)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FormatSettings { /// Enabled by default pub enabled: bool, @@ -93,11 +87,8 @@ impl Default for FormatSettings { } /// Linter settings for the entire workspace -#[derive(Debug)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct LinterSettings { /// Enabled by default pub enabled: bool, @@ -116,32 +107,29 @@ impl Default for LinterSettings { } /// Static map of language names to language-specific settings -#[derive(Debug, Default)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct LanguagesSettings { - #[cfg_attr(feature = "serde_workspace", serde(default))] + #[serde(default)] pub javascript: LanguageSettings, } pub trait Language: rome_rowan::Language { + #[cfg(not(feature = "schemars"))] /// Formatter settings type for this language - #[cfg(not(feature = "serde_workspace"))] - type FormatSettings: Default; + type FormatSettings: Default + serde::Serialize + serde::de::DeserializeOwned; + #[cfg(feature = "schemars")] /// Formatter settings type for this language - #[cfg(feature = "serde_workspace")] type FormatSettings: Default + serde::Serialize + serde::de::DeserializeOwned + schemars::JsonSchema; + #[cfg(not(feature = "schemars"))] /// Linter settings type for this language - #[cfg(not(feature = "serde_workspace"))] - type LinterSettings: Default; + type LinterSettings: Default + serde::Serialize + serde::de::DeserializeOwned; + #[cfg(feature = "schemars")] /// Linter settings type for this language - #[cfg(feature = "serde_workspace")] type LinterSettings: Default + serde::Serialize + serde::de::DeserializeOwned @@ -162,28 +150,22 @@ pub trait Language: rome_rowan::Language { ) -> Self::FormatContext; } -#[derive(Debug, Default)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct LanguageSettings { /// Formatter settings for this language - #[cfg_attr(feature = "serde_workspace", serde(default))] + #[serde(default)] pub format: L::FormatSettings, /// Linter settings for this language - #[cfg_attr(feature = "serde_workspace", serde(default))] + #[serde(default)] pub linter: L::LinterSettings, /// Globals variables/bindings that can be found in a file - #[cfg_attr( - feature = "serde_workspace", - serde( - default, - deserialize_with = "crate::configuration::deserialize_globals", - serialize_with = "crate::configuration::serialize_globals" - ) + #[serde( + default, + deserialize_with = "crate::configuration::deserialize_globals", + serialize_with = "crate::configuration::serialize_globals" )] pub globals: IndexSet, } diff --git a/crates/rome_service/src/workspace.rs b/crates/rome_service/src/workspace.rs index 6bc20ba8c1a..759be1af8f1 100644 --- a/crates/rome_service/src/workspace.rs +++ b/crates/rome_service/src/workspace.rs @@ -59,180 +59,144 @@ use rome_diagnostics::{CodeSuggestion, Diagnostic}; use rome_formatter::Printed; use rome_fs::RomePath; use rome_js_syntax::{TextRange, TextSize}; -#[cfg(feature = "serde_workspace")] +#[cfg(feature = "schemars")] use rome_rowan::TextRangeSchema; use rome_text_edit::Indel; -use std::{borrow::Cow, panic::RefUnwindSafe}; +use std::{borrow::Cow, panic::RefUnwindSafe, sync::Arc}; +pub use self::client::WorkspaceTransport; + +mod client; pub(crate) mod server; -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct SupportsFeatureParams { pub path: RomePath, pub feature: FeatureName, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum FeatureName { Format, Lint, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct UpdateSettingsParams { pub settings: WorkspaceSettings, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct OpenFileParams { pub path: RomePath, pub content: String, pub version: i32, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct GetSyntaxTreeParams { pub path: RomePath, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct GetSyntaxTreeResult { pub cst: String, pub ast: String, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct GetControlFlowGraphParams { pub path: RomePath, - #[cfg_attr(feature = "serde_workspace", schemars(with = "u32"))] + #[cfg_attr(feature = "schemars", schemars(with = "u32"))] pub cursor: TextSize, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct GetFormatterIRParams { pub path: RomePath, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ChangeFileParams { pub path: RomePath, pub content: String, pub version: i32, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct CloseFileParams { pub path: RomePath, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PullDiagnosticsParams { pub path: RomePath, pub categories: RuleCategories, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PullDiagnosticsResult { pub diagnostics: Vec, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PullActionsParams { pub path: RomePath, - #[cfg_attr(feature = "serde_workspace", schemars(with = "TextRangeSchema"))] + #[cfg_attr(feature = "schemars", schemars(with = "TextRangeSchema"))] pub range: TextRange, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PullActionsResult { pub actions: Vec, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct CodeAction { pub category: ActionCategory, pub rule_name: Cow<'static, str>, pub suggestion: CodeSuggestion, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FormatFileParams { pub path: RomePath, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FormatRangeParams { pub path: RomePath, - #[cfg_attr(feature = "serde_workspace", schemars(with = "TextRangeSchema"))] + #[cfg_attr(feature = "schemars", schemars(with = "TextRangeSchema"))] pub range: TextRange, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FormatOnTypeParams { pub path: RomePath, - #[cfg_attr(feature = "serde_workspace", schemars(with = "u32"))] + #[cfg_attr(feature = "schemars", schemars(with = "u32"))] pub offset: TextSize, } -#[derive(Clone, Copy)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] /// Which fixes should be applied during the analyzing phase pub enum FixFileMode { /// Applies [safe](rome_diagnostics::Applicability::Always) fixes @@ -241,20 +205,15 @@ pub enum FixFileMode { SafeAndSuggestedFixes, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FixFileParams { pub path: RomePath, pub fix_file_mode: FixFileMode, } -#[derive(Debug)] -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FixFileResult { /// New source code for the file with all fixes applied pub code: String, @@ -265,37 +224,30 @@ pub struct FixFileResult { pub skipped_suggested_fixes: u32, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] -#[derive(Debug)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FixAction { /// Name of the rule that emitted this code action pub rule_name: Cow<'static, str>, /// Source range at which this action was applied - #[cfg_attr(feature = "serde_workspace", schemars(with = "TextRangeSchema"))] + #[cfg_attr(feature = "schemars", schemars(with = "TextRangeSchema"))] pub range: TextRange, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct RenameParams { pub path: RomePath, - #[cfg_attr(feature = "serde_workspace", schemars(with = "u32"))] + #[cfg_attr(feature = "schemars", schemars(with = "u32"))] pub symbol_at: TextSize, pub new_name: String, } -#[cfg_attr( - feature = "serde_workspace", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct RenameResult { /// Range of source code modified by this rename operation - #[cfg_attr(feature = "serde_workspace", schemars(with = "TextRangeSchema"))] + #[cfg_attr(feature = "schemars", schemars(with = "TextRangeSchema"))] pub range: TextRange, /// List of text edit operations to apply on the source code pub indels: Vec, @@ -367,6 +319,19 @@ pub fn server() -> Box { Box::new(server::WorkspaceServer::new()) } +/// Convenience function for constructing a server instance of [Workspace] +pub fn server_sync() -> Arc { + Arc::new(server::WorkspaceServer::new()) +} + +/// Convenience function for constructing a client instance of [Workspace] +pub fn client(transport: T) -> Result, RomeError> +where + T: WorkspaceTransport + RefUnwindSafe + Send + Sync + 'static, +{ + Ok(Box::new(client::WorkspaceClient::new(transport)?)) +} + /// [RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization) /// guard for an open file in a workspace, takes care of closing the file /// automatically on drop diff --git a/crates/rome_service/src/workspace/client.rs b/crates/rome_service/src/workspace/client.rs new file mode 100644 index 00000000000..6e3443e43a9 --- /dev/null +++ b/crates/rome_service/src/workspace/client.rs @@ -0,0 +1,204 @@ +use std::{ + panic::RefUnwindSafe, + sync::{ + atomic::{AtomicU64, Ordering}, + Mutex, + }, +}; + +use rome_formatter::Printed; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::{from_slice, json, to_vec}; + +use crate::{RomeError, TransportError, Workspace}; + +use super::{ + ChangeFileParams, CloseFileParams, FixFileParams, FixFileResult, FormatFileParams, + FormatOnTypeParams, FormatRangeParams, GetControlFlowGraphParams, GetFormatterIRParams, + GetSyntaxTreeParams, GetSyntaxTreeResult, OpenFileParams, PullActionsParams, PullActionsResult, + PullDiagnosticsParams, PullDiagnosticsResult, RenameParams, RenameResult, + SupportsFeatureParams, UpdateSettingsParams, +}; + +pub(super) struct WorkspaceClient { + transport: Mutex, + request_id: AtomicU64, +} + +pub trait WorkspaceTransport { + fn send(&mut self, request: Vec) -> Result<(), TransportError>; + fn receive(&mut self) -> Result, TransportError>; +} + +#[derive(Serialize)] +struct JsonRpcRequest

{ + jsonrpc: &'static str, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + method: &'static str, + params: P, +} + +#[derive(Deserialize)] +struct JsonRpcResponse<'a, R> { + #[allow(dead_code)] + jsonrpc: &'a str, + id: u64, + #[serde(flatten)] + status: JsonRpcResult, +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum JsonRpcResult { + Ok { result: R }, + Err { error: JsonRpcError }, +} + +#[derive(Deserialize)] +struct JsonRpcError { + #[allow(dead_code)] + code: i64, + message: String, + data: Option, +} + +#[derive(Deserialize)] +struct InitializeResult {} + +impl WorkspaceClient +where + T: WorkspaceTransport + RefUnwindSafe + Send + Sync, +{ + pub(super) fn new(transport: T) -> Result { + let client = Self { + transport: Mutex::new(transport), + request_id: AtomicU64::new(0), + }; + + // TODO: The current implementation of the JSON-RPC protocol in + // tower_lsp doesn't allow any request to be sent before a call to + // initialize, this is something we could be able to lift by using our + // own RPC protocol implementation + let _value: InitializeResult = client.request( + "initialize", + json!({ + "capabilities": {}, + "client_info": { + "name": "rome_service", + "version": env!("CARGO_PKG_VERSION") + }, + }), + )?; + + Ok(client) + } + + fn request(&self, method: &'static str, params: P) -> Result + where + P: Serialize, + R: DeserializeOwned, + { + let mut transport = self.transport.lock().unwrap(); + + let id = self.request_id.fetch_add(1, Ordering::Relaxed); + let request = JsonRpcRequest { + jsonrpc: "2.0", + id: Some(id), + method, + params, + }; + + let request = to_vec(&request).map_err(TransportError::from)?; + transport.send(request)?; + + let response = transport.receive()?; + let response: JsonRpcResponse = from_slice(&response).map_err(TransportError::from)?; + + // This should be true since we don't allow concurrent requests yet + assert_eq!(response.id, id); + + match response.status { + JsonRpcResult::Ok { result } => Ok(result), + JsonRpcResult::Err { error } => match error.data { + Some(error) => Err(error), + None => Err(RomeError::from(TransportError::RPCError(error.message))), + }, + } + } +} + +impl Workspace for WorkspaceClient +where + T: WorkspaceTransport + RefUnwindSafe + Send + Sync, +{ + fn supports_feature(&self, params: SupportsFeatureParams) -> bool { + self.request("rome/supports_feature", params) + .unwrap_or(false) + } + + fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), RomeError> { + self.request("rome/update_settings", params) + } + + fn open_file(&self, params: OpenFileParams) -> Result<(), RomeError> { + self.request("rome/open_file", params) + } + + fn get_syntax_tree( + &self, + params: GetSyntaxTreeParams, + ) -> Result { + self.request("rome/get_syntax_tree", params) + } + + fn get_control_flow_graph( + &self, + params: GetControlFlowGraphParams, + ) -> Result { + self.request("rome/get_control_flow_graph", params) + } + + fn get_formatter_ir(&self, params: GetFormatterIRParams) -> Result { + self.request("rome/get_formatter_ir", params) + } + + fn change_file(&self, params: ChangeFileParams) -> Result<(), RomeError> { + self.request("rome/change_file", params) + } + + fn close_file(&self, params: CloseFileParams) -> Result<(), RomeError> { + self.request("rome/close_file", params) + } + + fn pull_diagnostics( + &self, + params: PullDiagnosticsParams, + ) -> Result { + self.request("rome/pull_diagnostics", params) + } + + fn pull_actions(&self, params: PullActionsParams) -> Result { + self.request("rome/pull_actions", params) + } + + fn format_file(&self, params: FormatFileParams) -> Result { + self.request("rome/format_file", params) + } + + fn format_range(&self, params: FormatRangeParams) -> Result { + self.request("rome/format_range", params) + } + + fn format_on_type(&self, params: FormatOnTypeParams) -> Result { + self.request("rome/format_on_type", params) + } + + fn fix_file(&self, params: FixFileParams) -> Result { + self.request("rome/fix_file", params) + } + + fn rename(&self, params: RenameParams) -> Result { + self.request("rome/rename", params) + } +} diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore index d60f723d525..7f15654662d 100644 --- a/editors/vscode/.vscodeignore +++ b/editors/vscode/.vscodeignore @@ -5,8 +5,8 @@ !out/main.js !package-lock.json !package.json -!server/rome_lsp -!server/rome_lsp.exe +!server/rome +!server/rome.exe !rome_syntax_tree.tmGrammar.json !configuration_schema.json !README.md \ No newline at end of file diff --git a/editors/vscode/src/commands/syntaxTree.ts b/editors/vscode/src/commands/syntaxTree.ts index 4a1409fe0a4..a56873ccb5d 100644 --- a/editors/vscode/src/commands/syntaxTree.ts +++ b/editors/vscode/src/commands/syntaxTree.ts @@ -86,6 +86,7 @@ class SyntaxTreeProvider if (document) { return document.value; } + const params: SyntaxTreeParams = { textDocument: { uri: this.session.editor.document.uri.toString() }, }; diff --git a/editors/vscode/src/lsp_requests.ts b/editors/vscode/src/lsp_requests.ts index dbedb2353b1..22121c47752 100644 --- a/editors/vscode/src/lsp_requests.ts +++ b/editors/vscode/src/lsp_requests.ts @@ -1,8 +1,4 @@ -import { - RequestType, - TextDocumentIdentifier, - Range, -} from "vscode-languageclient"; +import { RequestType, TextDocumentIdentifier } from "vscode-languageclient"; export interface SyntaxTreeParams { textDocument: TextDocumentIdentifier; @@ -15,4 +11,4 @@ export const syntaxTreeRequest = new RequestType< SyntaxTreeParams, string, void ->("rome/syntaxTree"); +>("rome_lsp/syntaxTree"); diff --git a/editors/vscode/src/main.ts b/editors/vscode/src/main.ts index a7a2b1e1d21..b0e2b3106f9 100644 --- a/editors/vscode/src/main.ts +++ b/editors/vscode/src/main.ts @@ -1,9 +1,11 @@ +import { spawn } from "child_process"; +import { connect } from "net"; import { ExtensionContext, Uri, window, workspace } from "vscode"; import { LanguageClient, LanguageClientOptions, ServerOptions, - TransportKind, + StreamInfo, } from "vscode-languageclient/node"; import { setContextValue } from "./utils"; import { Session } from "./session"; @@ -26,21 +28,13 @@ export async function activate(context: ExtensionContext) { return; } - const serverOptions: ServerOptions = { + const serverOptions: ServerOptions = createMessageTransports.bind( + undefined, command, - transport: TransportKind.stdio, - }; + ); const traceOutputChannel = window.createOutputChannel("Rome Trace"); - // only override serverOptions.options when developing extension, - // this is convenient for debugging - // Before, every time we modify the client package, we need to rebuild vscode extension and install, for now, we could use Launching Client or press F5 to open a separate debug window and doing some check, finally we could bundle the vscode and do some final check. - // Passing such variable via `Launch.json`, you need not to add an extra environment variable or change the setting.json `rome.lspBin`, - if (process.env.DEBUG_SERVER_PATH) { - serverOptions.options = { env: { ...process.env } }; - } - const clientOptions: LanguageClientOptions = { documentSelector: [ { scheme: "file", language: "javascript" }, @@ -95,7 +89,7 @@ async function getServerPath( } const binaryExt = triplet.includes("windows") ? ".exe" : ""; - const binaryName = `rome_lsp${binaryExt}`; + const binaryName = `rome${binaryExt}`; const bundlePath = Uri.joinPath(context.extensionUri, "server", binaryName); const bundleExists = await fileExists(bundlePath); @@ -116,6 +110,42 @@ async function fileExists(path: Uri) { } } +function getSocket(command: string): Promise { + return new Promise((resolve, reject) => { + const process = spawn(command, ["__print_socket"], { + stdio: "pipe", + }); + + process.on("error", reject); + + let pipeName = ""; + process.stdout.on("data", (data) => { + pipeName += data.toString("utf-8"); + }); + + process.on("exit", (code) => { + if (code === 0) { + console.log(`"${pipeName}"`); + resolve(pipeName.trimEnd()); + } else { + reject(code); + } + }); + }); +} + +async function createMessageTransports(command: string): Promise { + const path = await getSocket(command); + const socket = connect(path); + + await new Promise((resolve, reject) => { + socket.once("error", reject); + socket.once("ready", resolve); + }); + + return { writer: socket, reader: socket }; +} + export function deactivate(): Thenable | undefined { if (!client) { return undefined; diff --git a/website/playground/Cargo.toml b/website/playground/Cargo.toml index 3974a064a32..20c1264df21 100644 --- a/website/playground/Cargo.toml +++ b/website/playground/Cargo.toml @@ -16,13 +16,13 @@ crate-type = ["cdylib"] [dependencies] wasm-bindgen = { version = "0.2.79", features = ["serde-serialize"] } js-sys = "0.3.58" -rome_service = { path = "../../crates/rome_service", features = ["serde_workspace"] } +rome_service = { path = "../../crates/rome_service" } rome_console = { path = "../../crates/rome_console" } rome_diagnostics = { path = "../../crates/rome_diagnostics" } [build-dependencies] serde_json = "1.0.79" -rome_service = { path = "../../crates/rome_service", features = ["serde_workspace"] } +rome_service = { path = "../../crates/rome_service", features = ["schemars"] } rome_rowan = { path = "../../crates/rome_rowan" } rome_js_factory = { path = "../../crates/rome_js_factory" } rome_js_formatter = { path = "../../crates/rome_js_formatter" } diff --git a/website/playground/package.json b/website/playground/package.json index 362f1f55859..cdd116ca1f9 100644 --- a/website/playground/package.json +++ b/website/playground/package.json @@ -9,7 +9,7 @@ "build:js": "tsc && vite build", "build:wasm": "wasm-pack build --target web --release", "build:wasm-dev": "wasm-pack build --target web --dev", - "format": "cargo run -p rome_cli --release -- format --write ./src", + "format": "cargo rome-cli format --write ./src", "format:rome": "rome format --write src .", "tsc": "tsc" },