mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor(lsp): Have JS drive TSC event loop in LSP (#23565)
This commit is contained in:
parent
439b3b8db9
commit
19c0633a94
7 changed files with 333 additions and 180 deletions
342
cli/lsp/tsc.rs
342
cli/lsp/tsc.rs
|
@ -8,6 +8,7 @@ use super::documents::DocumentsFilter;
|
|||
use super::language_server;
|
||||
use super::language_server::StateSnapshot;
|
||||
use super::performance::Performance;
|
||||
use super::performance::PerformanceMark;
|
||||
use super::refactor::RefactorCodeActionData;
|
||||
use super::refactor::ALL_KNOWN_REFACTOR_ACTION_KINDS;
|
||||
use super::refactor::EXTRACT_CONSTANT;
|
||||
|
@ -28,16 +29,19 @@ use crate::tsc::ResolveArgs;
|
|||
use crate::tsc::MISSING_DEPENDENCY_SPECIFIER;
|
||||
use crate::util::path::relative_specifier;
|
||||
use crate::util::path::to_percent_decoded_str;
|
||||
use crate::util::result::InfallibleResultExt;
|
||||
use crate::util::v8::convert;
|
||||
use deno_core::convert::Smi;
|
||||
use deno_core::convert::ToV8;
|
||||
use deno_core::error::StdAnyError;
|
||||
use deno_runtime::fs_util::specifier_to_file_path;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use deno_ast::MediaType;
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::anyhow::Context as _;
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::located_script_name;
|
||||
use deno_core::op2;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::resolve_url;
|
||||
|
@ -63,9 +67,11 @@ use regex::Captures;
|
|||
use regex::Regex;
|
||||
use serde_repr::Deserialize_repr;
|
||||
use serde_repr::Serialize_repr;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
|
@ -244,6 +250,16 @@ pub enum ChangeKind {
|
|||
Closed = 2,
|
||||
}
|
||||
|
||||
impl<'a> ToV8<'a> for ChangeKind {
|
||||
type Error = Infallible;
|
||||
fn to_v8(
|
||||
self,
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||
Smi(self as u8).to_v8(scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ChangeKind {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
@ -260,15 +276,28 @@ pub struct PendingChange {
|
|||
pub config_changed: bool,
|
||||
}
|
||||
|
||||
impl PendingChange {
|
||||
fn to_v8<'s>(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
) -> Result<v8::Local<'s, v8::Value>, AnyError> {
|
||||
let modified_scripts = serde_v8::to_v8(scope, &self.modified_scripts)?;
|
||||
impl<'a> ToV8<'a> for PendingChange {
|
||||
type Error = Infallible;
|
||||
fn to_v8(
|
||||
self,
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||
let modified_scripts = {
|
||||
let mut modified_scripts_v8 =
|
||||
Vec::with_capacity(self.modified_scripts.len());
|
||||
for (specifier, kind) in &self.modified_scripts {
|
||||
let specifier = v8::String::new(scope, specifier).unwrap().into();
|
||||
let kind = kind.to_v8(scope).unwrap_infallible();
|
||||
let pair =
|
||||
v8::Array::new_with_elements(scope, &[specifier, kind]).into();
|
||||
modified_scripts_v8.push(pair);
|
||||
}
|
||||
v8::Array::new_with_elements(scope, &modified_scripts_v8).into()
|
||||
};
|
||||
let project_version =
|
||||
v8::Integer::new_from_unsigned(scope, self.project_version as u32).into();
|
||||
let config_changed = v8::Boolean::new(scope, self.config_changed).into();
|
||||
|
||||
Ok(
|
||||
v8::Array::new_with_elements(
|
||||
scope,
|
||||
|
@ -277,7 +306,9 @@ impl PendingChange {
|
|||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingChange {
|
||||
fn coalesce(
|
||||
&mut self,
|
||||
new_version: usize,
|
||||
|
@ -1068,6 +1099,7 @@ impl TsServer {
|
|||
let droppable_token = DroppableToken(token.clone());
|
||||
let (tx, mut rx) = oneshot::channel::<Result<String, AnyError>>();
|
||||
let change = self.pending_change.lock().take();
|
||||
|
||||
if self
|
||||
.sender
|
||||
.send((req, snapshot, tx, token.clone(), change))
|
||||
|
@ -3951,10 +3983,12 @@ struct State {
|
|||
last_id: usize,
|
||||
performance: Arc<Performance>,
|
||||
// the response from JS, as a JSON string
|
||||
response: Option<String>,
|
||||
response_tx: Option<oneshot::Sender<Result<String, AnyError>>>,
|
||||
state_snapshot: Arc<StateSnapshot>,
|
||||
specifier_map: Arc<TscSpecifierMap>,
|
||||
token: CancellationToken,
|
||||
pending_requests: Option<UnboundedReceiver<Request>>,
|
||||
mark: Option<PerformanceMark>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -3962,14 +3996,17 @@ impl State {
|
|||
state_snapshot: Arc<StateSnapshot>,
|
||||
specifier_map: Arc<TscSpecifierMap>,
|
||||
performance: Arc<Performance>,
|
||||
pending_requests: UnboundedReceiver<Request>,
|
||||
) -> Self {
|
||||
Self {
|
||||
last_id: 1,
|
||||
performance,
|
||||
response: None,
|
||||
response_tx: None,
|
||||
state_snapshot,
|
||||
specifier_map,
|
||||
token: Default::default(),
|
||||
mark: None,
|
||||
pending_requests: Some(pending_requests),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4090,6 +4127,75 @@ fn op_resolve(
|
|||
op_resolve_inner(state, ResolveArgs { base, specifiers })
|
||||
}
|
||||
|
||||
struct TscRequestArray {
|
||||
request: TscRequest,
|
||||
id: Smi<usize>,
|
||||
change: convert::OptionNull<PendingChange>,
|
||||
}
|
||||
|
||||
impl<'a> ToV8<'a> for TscRequestArray {
|
||||
type Error = StdAnyError;
|
||||
|
||||
fn to_v8(
|
||||
self,
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||
let id = self.id.to_v8(scope).unwrap_infallible();
|
||||
|
||||
let (method_name, args) = self.request.to_server_request(scope)?;
|
||||
|
||||
let method_name = deno_core::FastString::from_static(method_name)
|
||||
.v8_string(scope)
|
||||
.into();
|
||||
let args = args.unwrap_or_else(|| v8::Array::new(scope, 0).into());
|
||||
|
||||
let change = self.change.to_v8(scope).unwrap_infallible();
|
||||
|
||||
Ok(
|
||||
v8::Array::new_with_elements(scope, &[id, method_name, args, change])
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
#[to_v8]
|
||||
async fn op_poll_requests(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
) -> convert::OptionNull<TscRequestArray> {
|
||||
let mut pending_requests = {
|
||||
let mut state = state.borrow_mut();
|
||||
let state = state.try_borrow_mut::<State>().unwrap();
|
||||
state.pending_requests.take().unwrap()
|
||||
};
|
||||
|
||||
let Some((request, snapshot, response_tx, token, change)) =
|
||||
pending_requests.recv().await
|
||||
else {
|
||||
return None.into();
|
||||
};
|
||||
|
||||
let mut state = state.borrow_mut();
|
||||
let state = state.try_borrow_mut::<State>().unwrap();
|
||||
state.pending_requests = Some(pending_requests);
|
||||
state.state_snapshot = snapshot;
|
||||
state.token = token;
|
||||
state.response_tx = Some(response_tx);
|
||||
let id = state.last_id;
|
||||
state.last_id += 1;
|
||||
let mark = state
|
||||
.performance
|
||||
.mark_with_args(format!("tsc.host.{}", request.method()), &request);
|
||||
state.mark = Some(mark);
|
||||
|
||||
Some(TscRequestArray {
|
||||
request,
|
||||
id: Smi(id),
|
||||
change: change.into(),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn op_resolve_inner(
|
||||
state: &mut OpState,
|
||||
|
@ -4118,9 +4224,25 @@ fn op_resolve_inner(
|
|||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_respond(state: &mut OpState, #[string] response: String) {
|
||||
fn op_respond(
|
||||
state: &mut OpState,
|
||||
#[string] response: String,
|
||||
#[string] error: String,
|
||||
) {
|
||||
let state = state.borrow_mut::<State>();
|
||||
state.response = Some(response);
|
||||
state.performance.measure(state.mark.take().unwrap());
|
||||
let response = if !error.is_empty() {
|
||||
Err(anyhow!("tsc error: {error}"))
|
||||
} else {
|
||||
Ok(response)
|
||||
};
|
||||
|
||||
let was_sent = state.response_tx.take().unwrap().send(response).is_ok();
|
||||
// Don't print the send error if the token is cancelled, it's expected
|
||||
// to fail in that case and this commonly occurs.
|
||||
if !was_sent && !state.token.is_cancelled() {
|
||||
lsp_warn!("Unable to send result to client.");
|
||||
}
|
||||
}
|
||||
|
||||
#[op2]
|
||||
|
@ -4216,121 +4338,36 @@ fn op_project_version(state: &mut OpState) -> usize {
|
|||
|
||||
struct TscRuntime {
|
||||
js_runtime: JsRuntime,
|
||||
server_request_fn_global: v8::Global<v8::Function>,
|
||||
server_main_loop_fn_global: v8::Global<v8::Function>,
|
||||
}
|
||||
|
||||
impl TscRuntime {
|
||||
fn new(mut js_runtime: JsRuntime) -> Self {
|
||||
let server_request_fn_global = {
|
||||
let server_main_loop_fn_global = {
|
||||
let context = js_runtime.main_context();
|
||||
let scope = &mut js_runtime.handle_scope();
|
||||
let context_local = v8::Local::new(scope, context);
|
||||
let global_obj = context_local.global(scope);
|
||||
let server_request_fn_str =
|
||||
v8::String::new_external_onebyte_static(scope, b"serverRequest")
|
||||
let server_main_loop_fn_str =
|
||||
v8::String::new_external_onebyte_static(scope, b"serverMainLoop")
|
||||
.unwrap();
|
||||
let server_request_fn = v8::Local::try_from(
|
||||
global_obj.get(scope, server_request_fn_str.into()).unwrap(),
|
||||
let server_main_loop_fn = v8::Local::try_from(
|
||||
global_obj
|
||||
.get(scope, server_main_loop_fn_str.into())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
v8::Global::new(scope, server_request_fn)
|
||||
v8::Global::new(scope, server_main_loop_fn)
|
||||
};
|
||||
Self {
|
||||
server_request_fn_global,
|
||||
server_main_loop_fn_global,
|
||||
js_runtime,
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a request into the runtime and return the JSON string containing the response.
|
||||
fn request(
|
||||
&mut self,
|
||||
state_snapshot: Arc<StateSnapshot>,
|
||||
request: TscRequest,
|
||||
change: Option<PendingChange>,
|
||||
token: CancellationToken,
|
||||
) -> Result<String, AnyError> {
|
||||
if token.is_cancelled() {
|
||||
return Err(anyhow!("Operation was cancelled."));
|
||||
}
|
||||
let (performance, id) = {
|
||||
let op_state = self.js_runtime.op_state();
|
||||
let mut op_state = op_state.borrow_mut();
|
||||
let state = op_state.borrow_mut::<State>();
|
||||
state.state_snapshot = state_snapshot;
|
||||
state.token = token;
|
||||
state.last_id += 1;
|
||||
let id = state.last_id;
|
||||
(state.performance.clone(), id)
|
||||
};
|
||||
let mark = performance
|
||||
.mark_with_args(format!("tsc.host.{}", request.method()), &request);
|
||||
|
||||
{
|
||||
let scope = &mut self.js_runtime.handle_scope();
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let server_request_fn =
|
||||
v8::Local::new(tc_scope, &self.server_request_fn_global);
|
||||
let undefined = v8::undefined(tc_scope).into();
|
||||
|
||||
let change = if let Some(change) = change {
|
||||
change.to_v8(tc_scope)?
|
||||
} else {
|
||||
v8::null(tc_scope).into()
|
||||
};
|
||||
|
||||
let (method, req_args) = request.to_server_request(tc_scope)?;
|
||||
let args = vec![
|
||||
v8::Integer::new(tc_scope, id as i32).into(),
|
||||
v8::String::new(tc_scope, method).unwrap().into(),
|
||||
req_args.unwrap_or_else(|| v8::Array::new(tc_scope, 0).into()),
|
||||
change,
|
||||
];
|
||||
|
||||
server_request_fn.call(tc_scope, undefined, &args);
|
||||
if tc_scope.has_caught() && !tc_scope.has_terminated() {
|
||||
if let Some(stack_trace) = tc_scope.stack_trace() {
|
||||
lsp_warn!(
|
||||
"Error during TS request \"{method}\":\n {}",
|
||||
stack_trace.to_rust_string_lossy(tc_scope),
|
||||
);
|
||||
} else if let Some(message) = tc_scope.message() {
|
||||
lsp_warn!(
|
||||
"Error during TS request \"{method}\":\n {}\n {}",
|
||||
message.get(tc_scope).to_rust_string_lossy(tc_scope),
|
||||
tc_scope
|
||||
.exception()
|
||||
.map(|exc| exc.to_rust_string_lossy(tc_scope))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
lsp_warn!(
|
||||
"Error during TS request \"{method}\":\n {}",
|
||||
tc_scope
|
||||
.exception()
|
||||
.map(|exc| exc.to_rust_string_lossy(tc_scope))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
tc_scope.rethrow();
|
||||
}
|
||||
}
|
||||
|
||||
let op_state = self.js_runtime.op_state();
|
||||
let mut op_state = op_state.borrow_mut();
|
||||
let state = op_state.borrow_mut::<State>();
|
||||
|
||||
performance.measure(mark);
|
||||
state.response.take().ok_or_else(|| {
|
||||
custom_error(
|
||||
"RequestError",
|
||||
"The response was not received for the request.",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn run_tsc_thread(
|
||||
mut request_rx: UnboundedReceiver<Request>,
|
||||
request_rx: UnboundedReceiver<Request>,
|
||||
performance: Arc<Performance>,
|
||||
specifier_map: Arc<TscSpecifierMap>,
|
||||
maybe_inspector_server: Option<Arc<InspectorServer>>,
|
||||
|
@ -4340,9 +4377,13 @@ fn run_tsc_thread(
|
|||
// supplied snapshot is an isolate that contains the TypeScript language
|
||||
// server.
|
||||
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![deno_tsc::init_ops(performance, specifier_map)],
|
||||
extensions: vec![deno_tsc::init_ops(
|
||||
performance,
|
||||
specifier_map,
|
||||
request_rx,
|
||||
)],
|
||||
startup_snapshot: Some(tsc::compiler_snapshot()),
|
||||
inspector: maybe_inspector_server.is_some(),
|
||||
inspector: has_inspector_server,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
@ -4355,40 +4396,53 @@ fn run_tsc_thread(
|
|||
}
|
||||
|
||||
let tsc_future = async {
|
||||
start_tsc(&mut tsc_runtime, false).unwrap();
|
||||
let (request_signal_tx, mut request_signal_rx) = mpsc::unbounded_channel::<()>();
|
||||
let tsc_runtime = Rc::new(tokio::sync::Mutex::new(TscRuntime::new(tsc_runtime)));
|
||||
// start_tsc(&mut tsc_runtime, false).unwrap();
|
||||
let tsc_runtime =
|
||||
Rc::new(tokio::sync::Mutex::new(TscRuntime::new(tsc_runtime)));
|
||||
let tsc_runtime_ = tsc_runtime.clone();
|
||||
|
||||
let event_loop_fut = async {
|
||||
loop {
|
||||
if has_inspector_server {
|
||||
tsc_runtime_.lock().await.js_runtime.run_event_loop(PollEventLoopOptions {
|
||||
if let Err(e) = tsc_runtime_
|
||||
.lock()
|
||||
.await
|
||||
.js_runtime
|
||||
.run_event_loop(PollEventLoopOptions {
|
||||
wait_for_inspector: false,
|
||||
pump_v8_message_loop: true,
|
||||
}).await.ok();
|
||||
})
|
||||
.await
|
||||
{
|
||||
log::error!("Error in TSC event loop: {e}");
|
||||
}
|
||||
request_signal_rx.recv_many(&mut vec![], 1000).await;
|
||||
}
|
||||
};
|
||||
tokio::pin!(event_loop_fut);
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
(maybe_request, mut tsc_runtime) = async { (request_rx.recv().await, tsc_runtime.lock().await) } => {
|
||||
if let Some((req, state_snapshot, tx, token, pending_change)) = maybe_request {
|
||||
let value = tsc_runtime.request(state_snapshot, req, pending_change, token.clone());
|
||||
request_signal_tx.send(()).unwrap();
|
||||
let was_sent = tx.send(value).is_ok();
|
||||
// Don't print the send error if the token is cancelled, it's expected
|
||||
// to fail in that case and this commonly occurs.
|
||||
if !was_sent && !token.is_cancelled() {
|
||||
lsp_warn!("Unable to send result to client.");
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = &mut event_loop_fut => {}
|
||||
let main_loop_fut = {
|
||||
let enable_debug = std::env::var("DENO_TSC_DEBUG")
|
||||
.map(|s| {
|
||||
let s = s.trim();
|
||||
s == "1" || s.eq_ignore_ascii_case("true")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let mut runtime = tsc_runtime.lock().await;
|
||||
let main_loop = runtime.server_main_loop_fn_global.clone();
|
||||
let args = {
|
||||
let scope = &mut runtime.js_runtime.handle_scope();
|
||||
let enable_debug_local =
|
||||
v8::Local::<v8::Value>::from(v8::Boolean::new(scope, enable_debug));
|
||||
[v8::Global::new(scope, enable_debug_local)]
|
||||
};
|
||||
|
||||
runtime.js_runtime.call_with_args(&main_loop, &args)
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = event_loop_fut => {},
|
||||
res = main_loop_fut => {
|
||||
if let Err(err) = res {
|
||||
log::error!("Error in TSC main loop: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4410,30 +4464,23 @@ deno_core::extension!(deno_tsc,
|
|||
op_script_version,
|
||||
op_ts_config,
|
||||
op_project_version,
|
||||
op_poll_requests,
|
||||
],
|
||||
options = {
|
||||
performance: Arc<Performance>,
|
||||
specifier_map: Arc<TscSpecifierMap>,
|
||||
request_rx: UnboundedReceiver<Request>,
|
||||
},
|
||||
state = |state, options| {
|
||||
state.put(State::new(
|
||||
Default::default(),
|
||||
options.specifier_map,
|
||||
options.performance,
|
||||
options.request_rx,
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
/// Instruct a language server runtime to start the language server and provide
|
||||
/// it with a minimal bootstrap configuration.
|
||||
fn start_tsc(runtime: &mut JsRuntime, debug: bool) -> Result<(), AnyError> {
|
||||
let init_config = json!({ "debug": debug });
|
||||
let init_src = format!("globalThis.serverInit({init_config});");
|
||||
|
||||
runtime.execute_script(located_script_name!(), init_src)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize_repr, Serialize_repr)]
|
||||
#[repr(u32)]
|
||||
pub enum CompletionTriggerKind {
|
||||
|
@ -5123,8 +5170,9 @@ mod tests {
|
|||
}
|
||||
|
||||
fn setup_op_state(state_snapshot: Arc<StateSnapshot>) -> OpState {
|
||||
let (_tx, rx) = mpsc::unbounded_channel();
|
||||
let state =
|
||||
State::new(state_snapshot, Default::default(), Default::default());
|
||||
State::new(state_snapshot, Default::default(), Default::default(), rx);
|
||||
let mut op_state = OpState::new(None);
|
||||
op_state.put(state);
|
||||
op_state
|
||||
|
|
|
@ -1079,19 +1079,69 @@ delete Object.prototype.__proto__;
|
|||
/**
|
||||
* @param {number} _id
|
||||
* @param {any} data
|
||||
* @param {any | null} error
|
||||
*/
|
||||
// TODO(bartlomieju): this feels needlessly generic, both type chcking
|
||||
// and language server use it with inefficient serialization. Id is not used
|
||||
// anyway...
|
||||
function respond(_id, data = null) {
|
||||
ops.op_respond(JSON.stringify(data));
|
||||
function respond(_id, data = null, error = null) {
|
||||
if (error) {
|
||||
ops.op_respond(
|
||||
"error",
|
||||
"stack" in error ? error.stack.toString() : error.toString(),
|
||||
);
|
||||
} else {
|
||||
ops.op_respond(JSON.stringify(data), "");
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {[[string, number][], number, boolean] } PendingChange */
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {T | null} Option<T> */
|
||||
|
||||
/** @returns {Promise<[number, string, any[], Option<PendingChange>] | null>} */
|
||||
async function pollRequests() {
|
||||
return await ops.op_poll_requests();
|
||||
}
|
||||
|
||||
let hasStarted = false;
|
||||
|
||||
/** @param {boolean} enableDebugLogging */
|
||||
async function serverMainLoop(enableDebugLogging) {
|
||||
if (hasStarted) {
|
||||
throw new Error("The language server has already been initialized.");
|
||||
}
|
||||
hasStarted = true;
|
||||
languageService = ts.createLanguageService(host, documentRegistry);
|
||||
setLogDebug(enableDebugLogging, "TSLS");
|
||||
debug("serverInit()");
|
||||
|
||||
while (true) {
|
||||
const request = await pollRequests();
|
||||
if (request === null) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
serverRequest(request[0], request[1], request[2], request[3]);
|
||||
} catch (err) {
|
||||
const reqString = "[" + request.map((v) =>
|
||||
JSON.stringify(v)
|
||||
).join(", ") + "]";
|
||||
error(
|
||||
`Error occurred processing request ${reqString} : ${
|
||||
"stack" in err ? err.stack : err
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
* @param {string} method
|
||||
* @param {any[]} args
|
||||
* @param {[[string, number][], number, boolean] | null} maybeChange
|
||||
* @param {PendingChange | null} maybeChange
|
||||
*/
|
||||
function serverRequest(id, method, args, maybeChange) {
|
||||
if (logDebug) {
|
||||
|
@ -1160,11 +1210,7 @@ delete Object.prototype.__proto__;
|
|||
if (
|
||||
!isCancellationError(e)
|
||||
) {
|
||||
if ("stack" in e) {
|
||||
error(e.stack);
|
||||
} else {
|
||||
error(e);
|
||||
}
|
||||
respond(id, {}, e);
|
||||
throw e;
|
||||
}
|
||||
return respond(id, {});
|
||||
|
@ -1181,11 +1227,7 @@ delete Object.prototype.__proto__;
|
|||
return respond(id, languageService[method](...args));
|
||||
} catch (e) {
|
||||
if (!isCancellationError(e)) {
|
||||
if ("stack" in e) {
|
||||
error(e.stack);
|
||||
} else {
|
||||
error(e);
|
||||
}
|
||||
respond(id, null, e);
|
||||
throw e;
|
||||
}
|
||||
return respond(id);
|
||||
|
@ -1198,18 +1240,6 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
}
|
||||
|
||||
let hasStarted = false;
|
||||
/** @param {{ debug: boolean; }} init */
|
||||
function serverInit({ debug: debugFlag }) {
|
||||
if (hasStarted) {
|
||||
throw new Error("The language server has already been initialized.");
|
||||
}
|
||||
hasStarted = true;
|
||||
languageService = ts.createLanguageService(host, documentRegistry);
|
||||
setLogDebug(debugFlag, "TSLS");
|
||||
debug("serverInit()");
|
||||
}
|
||||
|
||||
// A build time only op that provides some setup information that is used to
|
||||
// ensure the snapshot is setup properly.
|
||||
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
|
||||
|
@ -1300,6 +1330,5 @@ delete Object.prototype.__proto__;
|
|||
|
||||
// exposes the functions that are called when the compiler is used as a
|
||||
// language service.
|
||||
global.serverInit = serverInit;
|
||||
global.serverRequest = serverRequest;
|
||||
global.serverMainLoop = serverMainLoop;
|
||||
})(this);
|
||||
|
|
|
@ -12,6 +12,7 @@ pub mod gitignore;
|
|||
pub mod logger;
|
||||
pub mod path;
|
||||
pub mod progress_bar;
|
||||
pub mod result;
|
||||
pub mod sync;
|
||||
pub mod text_encoding;
|
||||
pub mod time;
|
||||
|
|
16
cli/util/result.rs
Normal file
16
cli/util/result.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
pub trait InfallibleResultExt<T> {
|
||||
fn unwrap_infallible(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> InfallibleResultExt<T> for Result<T, Infallible> {
|
||||
fn unwrap_infallible(self) -> T {
|
||||
match self {
|
||||
Ok(value) => value,
|
||||
Err(never) => match never {},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
pub mod convert;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_v8_flags_from_env() -> Vec<String> {
|
||||
std::env::var("DENO_V8_FLAGS")
|
||||
|
|
57
cli/util/v8/convert.rs
Normal file
57
cli/util/v8/convert.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::v8;
|
||||
use deno_core::FromV8;
|
||||
use deno_core::ToV8;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
/// A wrapper type for `Option<T>` that (de)serializes `None` as `null`
|
||||
#[repr(transparent)]
|
||||
pub struct OptionNull<T>(pub Option<T>);
|
||||
|
||||
impl<T> From<Option<T>> for OptionNull<T> {
|
||||
fn from(option: Option<T>) -> Self {
|
||||
Self(option)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<OptionNull<T>> for Option<T> {
|
||||
fn from(value: OptionNull<T>) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ToV8<'a> for OptionNull<T>
|
||||
where
|
||||
T: ToV8<'a>,
|
||||
{
|
||||
type Error = T::Error;
|
||||
|
||||
fn to_v8(
|
||||
self,
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||
match self.0 {
|
||||
Some(value) => value.to_v8(scope),
|
||||
None => Ok(v8::null(scope).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromV8<'a> for OptionNull<T>
|
||||
where
|
||||
T: FromV8<'a>,
|
||||
{
|
||||
type Error = T::Error;
|
||||
|
||||
fn from_v8(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
value: v8::Local<'a, v8::Value>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
if value.is_null() {
|
||||
Ok(OptionNull(None))
|
||||
} else {
|
||||
T::from_v8(scope, value).map(|v| OptionNull(Some(v)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -895,7 +895,7 @@ fn repl_with_quiet_flag() {
|
|||
assert!(!out.contains("Deno"));
|
||||
assert!(!out.contains("exit using ctrl+d, ctrl+c, or close()"));
|
||||
assert_ends_with!(out, "\"done\"\n");
|
||||
assert!(err.is_empty());
|
||||
assert!(err.is_empty(), "Error: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -959,7 +959,7 @@ fn npm_packages() {
|
|||
);
|
||||
|
||||
assert_contains!(out, "hello");
|
||||
assert!(err.is_empty());
|
||||
assert!(err.is_empty(), "Error: {}", err);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -975,7 +975,7 @@ fn npm_packages() {
|
|||
);
|
||||
|
||||
assert_contains!(out, "hello");
|
||||
assert!(err.is_empty());
|
||||
assert!(err.is_empty(), "Error: {}", err);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -989,7 +989,7 @@ fn npm_packages() {
|
|||
|
||||
assert_contains!(out, "[Module: null prototype] {");
|
||||
assert_contains!(out, "Chalk: [class Chalk],");
|
||||
assert!(err.is_empty());
|
||||
assert!(err.is_empty(), "Error: {}", err);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1005,7 +1005,7 @@ fn npm_packages() {
|
|||
out,
|
||||
"error: npm package 'asdfawe52345asdf' does not exist"
|
||||
);
|
||||
assert!(err.is_empty());
|
||||
assert!(err.is_empty(), "Error: {}", err);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1021,7 +1021,7 @@ fn npm_packages() {
|
|||
);
|
||||
|
||||
assert_contains!(out, "no");
|
||||
assert!(err.is_empty());
|
||||
assert!(err.is_empty(), "Error: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue