Skip to content

Commit

Permalink
feat(lsp): add experimental testing API (denoland#13798)
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Mar 29, 2022
1 parent 4a0b2c2 commit 061090d
Show file tree
Hide file tree
Showing 20 changed files with 2,607 additions and 51 deletions.
8 changes: 4 additions & 4 deletions cli/bench/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct FixtureMessage {
/// the end of the document and does a level of hovering and gets quick fix
/// code actions.
fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
let mut client = LspClient::new(deno_exe)?;
let mut client = LspClient::new(deno_exe, false)?;

let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
let (_, response_error): (Option<Value>, Option<LspResponseError>) =
Expand Down Expand Up @@ -125,7 +125,7 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
}

fn bench_code_lens(deno_exe: &Path) -> Result<Duration, AnyError> {
let mut client = LspClient::new(deno_exe)?;
let mut client = LspClient::new(deno_exe, false)?;

let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
let (_, maybe_err) =
Expand Down Expand Up @@ -189,7 +189,7 @@ fn bench_code_lens(deno_exe: &Path) -> Result<Duration, AnyError> {
}

fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
let mut client = LspClient::new(deno_exe)?;
let mut client = LspClient::new(deno_exe, false)?;

let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
let (_, maybe_err) =
Expand Down Expand Up @@ -285,7 +285,7 @@ fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {

/// A test that starts up the LSP, opens a single line document, and exits.
fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> {
let mut client = LspClient::new(deno_exe)?;
let mut client = LspClient::new(deno_exe, false)?;

let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
let (_, response_error) =
Expand Down
2 changes: 1 addition & 1 deletion cli/bench/lsp_bench_standalone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use test_util::lsp::LspClient;
// https://github.com/quick-lint/quick-lint-js/blob/35207e6616267c6c81be63f47ce97ec2452d60df/benchmark/benchmark-lsp/lsp-benchmarks.cpp#L223-L268
fn incremental_change_wait(bench: &mut Bencher) {
let deno_exe = test_util::deno_exe_path();
let mut client = LspClient::new(&deno_exe).unwrap();
let mut client = LspClient::new(&deno_exe, false).unwrap();

static FIXTURE_INIT_JSON: &[u8] =
include_bytes!("testdata/initialize_params.json");
Expand Down
6 changes: 5 additions & 1 deletion cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,11 @@ To evaluate code in the shell:
";

/// Main entry point for parsing deno's command line flags.
pub fn flags_from_vec(args: Vec<String>) -> clap::Result<Flags> {
pub fn flags_from_vec<I, T>(args: I) -> clap::Result<Flags>
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
{
let version = crate::version::deno();
let app = clap_root(&version);
let matches = app.clone().try_get_matches_from(args)?;
Expand Down
1 change: 1 addition & 0 deletions cli/lsp/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ pub fn server_capabilities(
moniker_provider: None,
experimental: Some(json!({
"denoConfigTasks": true,
"testingApi":true,
})),
}
}
40 changes: 40 additions & 0 deletions cli/lsp/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ use crate::lsp::repl::get_repl_workspace_settings;
use super::config::SpecifierSettings;
use super::config::SETTINGS_SECTION;
use super::lsp_custom;
use super::testing::lsp_custom as testing_lsp_custom;

#[derive(Debug)]
pub enum TestingNotification {
Module(testing_lsp_custom::TestModuleNotificationParams),
DeleteModule(testing_lsp_custom::TestModuleDeleteNotificationParams),
Progress(testing_lsp_custom::TestRunProgressParams),
}

#[derive(Clone)]
pub struct Client(Arc<dyn ClientTrait>);
Expand Down Expand Up @@ -51,6 +59,10 @@ impl Client {
self.0.send_registry_state_notification(params).await;
}

pub fn send_test_notification(&self, params: TestingNotification) {
self.0.send_test_notification(params);
}

pub async fn specifier_configurations(
&self,
specifiers: Vec<lsp::Url>,
Expand Down Expand Up @@ -118,6 +130,7 @@ trait ClientTrait: Send + Sync {
&self,
params: lsp_custom::RegistryStateNotificationParams,
) -> AsyncReturn<()>;
fn send_test_notification(&self, params: TestingNotification);
fn specifier_configurations(
&self,
uris: Vec<lsp::Url>,
Expand Down Expand Up @@ -164,6 +177,31 @@ impl ClientTrait for LspowerClient {
})
}

fn send_test_notification(&self, notification: TestingNotification) {
let client = self.0.clone();
tokio::task::spawn(async move {
match notification {
TestingNotification::Module(params) => {
client
.send_custom_notification::<testing_lsp_custom::TestModuleNotification>(
params,
)
.await
}
TestingNotification::DeleteModule(params) => client
.send_custom_notification::<testing_lsp_custom::TestModuleDeleteNotification>(
params,
)
.await,
TestingNotification::Progress(params) => client
.send_custom_notification::<testing_lsp_custom::TestRunProgressNotification>(
params,
)
.await,
}
});
}

fn specifier_configurations(
&self,
uris: Vec<lsp::Url>,
Expand Down Expand Up @@ -260,6 +298,8 @@ impl ClientTrait for ReplClient {
Box::pin(future::ready(()))
}

fn send_test_notification(&self, _params: TestingNotification) {}

fn specifier_configurations(
&self,
uris: Vec<lsp::Url>,
Expand Down
39 changes: 38 additions & 1 deletion cli/lsp/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ pub struct ClientCapabilities {
pub code_action_disabled_support: bool,
pub line_folding_only: bool,
pub status_notification: bool,
/// The client provides the `experimental.testingApi` capability, which is
/// built around VSCode's testing API. It indicates that the server should
/// send notifications about tests discovered in modules.
pub testing_api: bool,
pub workspace_configuration: bool,
pub workspace_did_change_watched_files: bool,
}
Expand Down Expand Up @@ -139,6 +143,28 @@ pub struct SpecifierSettings {
pub code_lens: CodeLensSpecifierSettings,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TestingSettings {
/// A vector of arguments which should be used when running the tests for
/// a workspace.
#[serde(default)]
pub args: Vec<String>,
/// Enable or disable the testing API if the client is capable of supporting
/// the testing API.
#[serde(default = "is_true")]
pub enable: bool,
}

impl Default for TestingSettings {
fn default() -> Self {
Self {
args: vec!["--allow-all".to_string(), "--no-check".to_string()],
enable: true,
}
}
}

/// Deno language server specific settings that are applied to a workspace.
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -184,6 +210,10 @@ pub struct WorkspaceSettings {
#[serde(default)]
pub suggest: CompletionSettings,

/// Testing settings for the workspace.
#[serde(default)]
pub testing: TestingSettings,

/// An option which sets the cert file to use when attempting to fetch remote
/// resources. This overrides `DENO_CERT` if present.
pub tls_certificate: Option<String>,
Expand Down Expand Up @@ -333,7 +363,10 @@ impl Config {
self.client_capabilities.status_notification = experimental
.get("statusNotification")
.and_then(|it| it.as_bool())
== Some(true)
== Some(true);
self.client_capabilities.testing_api =
experimental.get("testingApi").and_then(|it| it.as_bool())
== Some(true);
}

if let Some(workspace) = &capabilities.workspace {
Expand Down Expand Up @@ -530,6 +563,10 @@ mod tests {
hosts: HashMap::new(),
}
},
testing: TestingSettings {
args: vec!["--allow-all".to_string(), "--no-check".to_string()],
enable: true
},
tls_certificate: None,
unsafely_ignore_certificate_errors: None,
unstable: false,
Expand Down
61 changes: 57 additions & 4 deletions cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use super::performance::Performance;
use super::refactor;
use super::registries::ModuleRegistry;
use super::registries::ModuleRegistryOptions;
use super::testing;
use super::text;
use super::tsc;
use super::tsc::Assets;
Expand Down Expand Up @@ -107,14 +108,16 @@ pub struct Inner {
/// An optional configuration file which has been specified in the client
/// options.
maybe_config_file: Option<ConfigFile>,
/// An optional configuration for linter which has been taken from specified config file.
pub maybe_lint_config: Option<LintConfig>,
/// An optional configuration for formatter which has been taken from specified config file.
maybe_fmt_config: Option<FmtConfig>,
/// An optional import map which is used to resolve modules.
pub maybe_import_map: Option<Arc<ImportMap>>,
/// The URL for the import map which is used to determine relative imports.
maybe_import_map_uri: Option<Url>,
/// An optional configuration for linter which has been taken from specified config file.
pub maybe_lint_config: Option<LintConfig>,
/// A lazily create "server" for handling test run requests.
maybe_testing_server: Option<testing::TestServer>,
/// A collection of measurements which instrument that performance of the LSP.
performance: Arc<Performance>,
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
Expand Down Expand Up @@ -163,12 +166,13 @@ impl Inner {
diagnostics_server,
documents,
maybe_cache_path: None,
maybe_lint_config: None,
maybe_fmt_config: None,
maybe_cache_server: None,
maybe_config_file: None,
maybe_import_map: None,
maybe_import_map_uri: None,
maybe_lint_config: None,
maybe_fmt_config: None,
maybe_testing_server: None,
module_registries,
module_registries_location,
performance,
Expand Down Expand Up @@ -781,6 +785,15 @@ impl Inner {
}
self.config.update_enabled_paths(self.client.clone()).await;

if self.config.client_capabilities.testing_api {
let test_server = testing::TestServer::new(
self.client.clone(),
self.performance.clone(),
self.config.root_uri.clone(),
);
self.maybe_testing_server = Some(test_server);
}

lsp_log!("Server ready.");
}

Expand Down Expand Up @@ -835,6 +848,7 @@ impl Inner {
.diagnostics_server
.invalidate(&self.documents.dependents(&specifier));
self.send_diagnostics_update();
self.send_testing_update();
}
}
Err(err) => error!("{}", err),
Expand All @@ -860,6 +874,7 @@ impl Inner {
specifiers.push(specifier.clone());
self.diagnostics_server.invalidate(&specifiers);
self.send_diagnostics_update();
self.send_testing_update();
}
self.performance.measure(mark);
}
Expand Down Expand Up @@ -909,6 +924,7 @@ impl Inner {
);

self.send_diagnostics_update();
self.send_testing_update();
}

async fn did_change_watched_files(
Expand Down Expand Up @@ -954,6 +970,7 @@ impl Inner {
);
self.diagnostics_server.invalidate_all();
self.send_diagnostics_update();
self.send_testing_update();
}
self.performance.measure(mark);
}
Expand Down Expand Up @@ -2143,6 +2160,29 @@ impl Inner {
self.reload_import_registries().await
}
lsp_custom::TASK_REQUEST => self.get_tasks(),
testing::TEST_RUN_REQUEST => {
if let Some(testing_server) = &self.maybe_testing_server {
match params.map(serde_json::from_value) {
Some(Ok(params)) => testing_server
.run_request(params, self.config.get_workspace_settings()),
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
None => Err(LspError::invalid_params("Missing parameters")),
}
} else {
Err(LspError::invalid_request())
}
}
testing::TEST_RUN_CANCEL_REQUEST => {
if let Some(testing_server) = &self.maybe_testing_server {
match params.map(serde_json::from_value) {
Some(Ok(params)) => testing_server.run_cancel_request(params),
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
None => Err(LspError::invalid_params("Missing parameters")),
}
} else {
Err(LspError::invalid_request())
}
}
lsp_custom::VIRTUAL_TEXT_DOCUMENT => {
match params.map(serde_json::from_value) {
Some(Ok(params)) => Ok(Some(
Expand Down Expand Up @@ -2389,6 +2429,16 @@ impl Inner {
error!("Cannot update diagnostics: {}", err);
}
}

/// Send a message to the testing server to look for any changes in tests and
/// update the client.
fn send_testing_update(&self) {
if let Some(testing_server) = &self.maybe_testing_server {
if let Err(err) = testing_server.update(self.snapshot()) {
error!("Cannot update testing server: {}", err);
}
}
}
}

#[lspower::async_trait]
Expand Down Expand Up @@ -2432,6 +2482,7 @@ impl lspower::LanguageServer for LanguageServer {
// don't send diagnostics yet if we don't have the specifier settings
if has_specifier_settings {
inner.send_diagnostics_update();
inner.send_testing_update();
}
}
(client, uri, specifier, has_specifier_settings)
Expand Down Expand Up @@ -2464,6 +2515,7 @@ impl lspower::LanguageServer for LanguageServer {
.unwrap_or(false)
{
inner.send_diagnostics_update();
inner.send_testing_update();
}
});
}
Expand Down Expand Up @@ -2823,6 +2875,7 @@ impl Inner {
// invalidate some diagnostics
self.diagnostics_server.invalidate(&[referrer]);
self.send_diagnostics_update();
self.send_testing_update();

self.performance.measure(mark);
Ok(Some(json!(true)))
Expand Down
1 change: 1 addition & 0 deletions cli/lsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod refactor;
mod registries;
mod repl;
mod semantic_tokens;
mod testing;
mod text;
mod tsc;
mod urls;
Expand Down
Loading

0 comments on commit 061090d

Please sign in to comment.