summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/README.md27
-rw-r--r--cli/lsp/code_lens.rs298
-rw-r--r--cli/lsp/config.rs49
-rw-r--r--cli/lsp/language_server.rs24
-rw-r--r--cli/tests/integration_tests_lsp.rs80
-rw-r--r--cli/tests/lsp/code_lens_response_test.json162
-rw-r--r--cli/tests/lsp/did_open_params_test_code_lens.json8
-rw-r--r--cli/tests/lsp/initialize_params.json3
-rw-r--r--cli/tests/lsp/initialize_params_code_lens_test.json56
-rw-r--r--cli/tests/lsp/initialize_params_code_lens_test_disabled.json61
10 files changed, 749 insertions, 19 deletions
diff --git a/cli/lsp/README.md b/cli/lsp/README.md
index 28ae50c6a..764998fb1 100644
--- a/cli/lsp/README.md
+++ b/cli/lsp/README.md
@@ -24,6 +24,7 @@ There are several settings that the language server supports for a workspace:
- `deno.codeLens.implementations`
- `deno.codeLens.references`
- `deno.codeLens.referencesAllFunctions`
+- `deno.codeLens.test`
- `deno.suggest.completeFunctionCalls`
- `deno.suggest.names`
- `deno.suggest.paths`
@@ -33,10 +34,11 @@ There are several settings that the language server supports for a workspace:
- `deno.lint`
- `deno.unstable`
-There are settings that are support on a per resource basis by the language
+There are settings that are supported on a per resource basis by the language
server:
- `deno.enable`
+- `deno.codeLens.test`
There are several points in the process where Deno analyzes these settings.
First, when the `initialize` request from the client, the
@@ -68,7 +70,24 @@ settings.
If the client does not have the `workspaceConfiguration` capability, the
language server will assume the workspace setting applies to all resources.
-## Custom requests
+## Commands
+
+There are several commands that might be issued by the language server to the
+client, which the client is expected to implement:
+
+- `deno.cache` - This is sent as a resolution code action when there is an
+ un-cached module specifier that is being imported into a module. It will be
+ sent with and argument that contains the resolved specifier as a string to be
+ cached.
+- `deno.showReferences` - This is sent as the command on some code lenses to
+ show locations of references. The arguments contain the specifier that is the
+ subject of the command, the start position of the target and the locations of
+ the references to show.
+- `deno.test` - This is sent as part of a test code lens to, of which the client
+ is expected to run a test based on the arguments, which are the specifier the
+ test is contained in and the name of the test to filter the tests on.
+
+## Requests
The LSP currently supports the following custom requests. A client should
implement these in order to have a fully functioning client that integrates well
@@ -115,9 +134,9 @@ with Deno:
}
```
-## Custom notifications
+## Notifications
-There is currently one custom notification that is send from the server to the
+There is currently one custom notification that is sent from the server to the
client:
- `deno/registryStatus` - when `deno.suggest.imports.autoDiscover` is `true` and
diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs
index 9be5bed2c..3ad644af3 100644
--- a/cli/lsp/code_lens.rs
+++ b/cli/lsp/code_lens.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+use super::analysis;
use super::language_server;
use super::tsc;
@@ -14,7 +15,14 @@ use deno_core::ModuleSpecifier;
use lspower::lsp;
use regex::Regex;
use std::cell::RefCell;
+use std::collections::HashSet;
use std::rc::Rc;
+use swc_common::SourceMap;
+use swc_common::Span;
+use swc_ecmascript::ast;
+use swc_ecmascript::visit::Node;
+use swc_ecmascript::visit::Visit;
+use swc_ecmascript::visit::VisitWith;
lazy_static::lazy_static! {
static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap();
@@ -36,6 +44,174 @@ pub struct CodeLensData {
pub specifier: ModuleSpecifier,
}
+fn span_to_range(span: &Span, source_map: Rc<SourceMap>) -> lsp::Range {
+ let start = source_map.lookup_char_pos(span.lo);
+ let end = source_map.lookup_char_pos(span.hi);
+ lsp::Range {
+ start: lsp::Position {
+ line: (start.line - 1) as u32,
+ character: start.col_display as u32,
+ },
+ end: lsp::Position {
+ line: (end.line - 1) as u32,
+ character: end.col_display as u32,
+ },
+ }
+}
+
+struct DenoTestCollector {
+ code_lenses: Vec<lsp::CodeLens>,
+ source_map: Rc<SourceMap>,
+ specifier: ModuleSpecifier,
+ test_vars: HashSet<String>,
+}
+
+impl DenoTestCollector {
+ pub fn new(specifier: ModuleSpecifier, source_map: Rc<SourceMap>) -> Self {
+ Self {
+ code_lenses: Vec::new(),
+ source_map,
+ specifier,
+ test_vars: HashSet::new(),
+ }
+ }
+
+ fn add_code_lens<N: AsRef<str>>(&mut self, name: N, span: &Span) {
+ let range = span_to_range(span, self.source_map.clone());
+ self.code_lenses.push(lsp::CodeLens {
+ range,
+ command: Some(lsp::Command {
+ title: "▶\u{fe0e} Run Test".to_string(),
+ command: "deno.test".to_string(),
+ arguments: Some(vec![json!(self.specifier), json!(name.as_ref())]),
+ }),
+ data: None,
+ });
+ }
+
+ fn check_call_expr(&mut self, node: &ast::CallExpr, span: &Span) {
+ if let Some(expr) = node.args.get(0).map(|es| es.expr.as_ref()) {
+ match expr {
+ ast::Expr::Object(obj_lit) => {
+ for prop in &obj_lit.props {
+ if let ast::PropOrSpread::Prop(prop) = prop {
+ if let ast::Prop::KeyValue(key_value_prop) = prop.as_ref() {
+ if let ast::PropName::Ident(ident) = &key_value_prop.key {
+ if ident.sym.to_string() == "name" {
+ if let ast::Expr::Lit(ast::Lit::Str(lit_str)) =
+ key_value_prop.value.as_ref()
+ {
+ let name = lit_str.value.to_string();
+ self.add_code_lens(name, &span);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ast::Expr::Lit(ast::Lit::Str(lit_str)) => {
+ let name = lit_str.value.to_string();
+ self.add_code_lens(name, &span);
+ }
+ _ => (),
+ }
+ }
+ }
+
+ /// Move out the code lenses from the collector.
+ fn take(self) -> Vec<lsp::CodeLens> {
+ self.code_lenses
+ }
+}
+
+impl Visit for DenoTestCollector {
+ fn visit_call_expr(&mut self, node: &ast::CallExpr, _parent: &dyn Node) {
+ if let ast::ExprOrSuper::Expr(callee_expr) = &node.callee {
+ match callee_expr.as_ref() {
+ ast::Expr::Ident(ident) => {
+ if self.test_vars.contains(&ident.sym.to_string()) {
+ self.check_call_expr(node, &ident.span);
+ }
+ }
+ ast::Expr::Member(member_expr) => {
+ if let ast::Expr::Ident(ns_prop_ident) = member_expr.prop.as_ref() {
+ if ns_prop_ident.sym.to_string() == "test" {
+ if let ast::ExprOrSuper::Expr(obj_expr) = &member_expr.obj {
+ if let ast::Expr::Ident(ident) = obj_expr.as_ref() {
+ if ident.sym.to_string() == "Deno" {
+ self.check_call_expr(node, &ns_prop_ident.span);
+ }
+ }
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+
+ fn visit_var_decl(&mut self, node: &ast::VarDecl, _parent: &dyn Node) {
+ for decl in &node.decls {
+ if let Some(init) = &decl.init {
+ match init.as_ref() {
+ // Identify destructured assignments of `test` from `Deno`
+ ast::Expr::Ident(ident) => {
+ if ident.sym.to_string() == "Deno" {
+ if let ast::Pat::Object(object_pat) = &decl.name {
+ for prop in &object_pat.props {
+ match prop {
+ ast::ObjectPatProp::Assign(prop) => {
+ let name = prop.key.sym.to_string();
+ if name == "test" {
+ self.test_vars.insert(name);
+ }
+ }
+ ast::ObjectPatProp::KeyValue(prop) => {
+ if let ast::PropName::Ident(key_ident) = &prop.key {
+ if key_ident.sym.to_string() == "test" {
+ if let ast::Pat::Ident(value_ident) =
+ &prop.value.as_ref()
+ {
+ self
+ .test_vars
+ .insert(value_ident.id.sym.to_string());
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+ }
+ // Identify variable assignments where the init is `Deno.test`
+ ast::Expr::Member(member_expr) => {
+ if let ast::ExprOrSuper::Expr(expr) = &member_expr.obj {
+ if let ast::Expr::Ident(obj_ident) = expr.as_ref() {
+ if obj_ident.sym.to_string() == "Deno" {
+ if let ast::Expr::Ident(prop_ident) =
+ &member_expr.prop.as_ref()
+ {
+ if prop_ident.sym.to_string() == "test" {
+ if let ast::Pat::Ident(binding_ident) = &decl.name {
+ self.test_vars.insert(binding_ident.id.sym.to_string());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
async fn resolve_implementation_code_lens(
code_lens: lsp::CodeLens,
data: CodeLensData,
@@ -189,8 +365,51 @@ pub(crate) async fn resolve_code_lens(
}
}
+pub(crate) async fn collect(
+ specifier: &ModuleSpecifier,
+ language_server: &mut language_server::Inner,
+) -> Result<Vec<lsp::CodeLens>, AnyError> {
+ let mut code_lenses = collect_test(specifier, language_server)?;
+ code_lenses.extend(collect_tsc(specifier, language_server).await?);
+
+ Ok(code_lenses)
+}
+
+fn collect_test(
+ specifier: &ModuleSpecifier,
+ language_server: &mut language_server::Inner,
+) -> Result<Vec<lsp::CodeLens>, AnyError> {
+ if language_server.config.specifier_code_lens_test(specifier) {
+ let source = language_server
+ .get_text_content(specifier)
+ .ok_or_else(|| anyhow!("Missing text content: {}", specifier))?;
+ let media_type = language_server
+ .get_media_type(specifier)
+ .ok_or_else(|| anyhow!("Missing media type: {}", specifier))?;
+ // we swallow parsed errors, as they are meaningless here.
+ // TODO(@kitsonk) consider caching previous code_lens results to return if
+ // there is a parse error to avoid issues of lenses popping in and out
+ if let Ok(parsed_module) =
+ analysis::parse_module(specifier, &source, &media_type)
+ {
+ let mut collector = DenoTestCollector::new(
+ specifier.clone(),
+ parsed_module.source_map.clone(),
+ );
+ parsed_module.module.visit_with(
+ &ast::Invalid {
+ span: swc_common::DUMMY_SP,
+ },
+ &mut collector,
+ );
+ return Ok(collector.take());
+ }
+ }
+ Ok(Vec::new())
+}
+
/// Return tsc navigation tree code lenses.
-pub(crate) async fn tsc_code_lenses(
+async fn collect_tsc(
specifier: &ModuleSpecifier,
language_server: &mut language_server::Inner,
) -> Result<Vec<lsp::CodeLens>, AnyError> {
@@ -282,3 +501,80 @@ pub(crate) async fn tsc_code_lenses(
});
Ok(Rc::try_unwrap(code_lenses).unwrap().into_inner())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::media_type::MediaType;
+
+ #[test]
+ fn test_deno_test_collector() {
+ let specifier = resolve_url("https://deno.land/x/mod.ts").unwrap();
+ let source = r#"
+ Deno.test({
+ name: "test a",
+ fn() {}
+ });
+
+ Deno.test("test b", function anotherTest() {});
+ "#;
+ let parsed_module =
+ analysis::parse_module(&specifier, source, &MediaType::TypeScript)
+ .unwrap();
+ let mut collector =
+ DenoTestCollector::new(specifier, parsed_module.source_map.clone());
+ parsed_module.module.visit_with(
+ &ast::Invalid {
+ span: swc_common::DUMMY_SP,
+ },
+ &mut collector,
+ );
+ assert_eq!(
+ collector.take(),
+ vec![
+ lsp::CodeLens {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 1,
+ character: 11
+ },
+ end: lsp::Position {
+ line: 1,
+ character: 15
+ }
+ },
+ command: Some(lsp::Command {
+ title: "▶\u{fe0e} Run Test".to_string(),
+ command: "deno.test".to_string(),
+ arguments: Some(vec![
+ json!("https://deno.land/x/mod.ts"),
+ json!("test a"),
+ ])
+ }),
+ data: None,
+ },
+ lsp::CodeLens {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 6,
+ character: 11
+ },
+ end: lsp::Position {
+ line: 6,
+ character: 15
+ }
+ },
+ command: Some(lsp::Command {
+ title: "▶\u{fe0e} Run Test".to_string(),
+ command: "deno.test".to_string(),
+ arguments: Some(vec![
+ json!("https://deno.land/x/mod.ts"),
+ json!("test b"),
+ ])
+ }),
+ data: None,
+ }
+ ]
+ );
+ }
+}
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index e46e8dd52..554bfd1e9 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -28,6 +28,10 @@ pub struct ClientCapabilities {
pub line_folding_only: bool,
}
+fn is_true() -> bool {
+ true
+}
+
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CodeLensSettings {
@@ -41,6 +45,10 @@ pub struct CodeLensSettings {
/// an impact, the `references` flag needs to be `true`.
#[serde(default)]
pub references_all_functions: bool,
+ /// Flag for providing test code lens on `Deno.test` statements. There is
+ /// also the `test_args` setting, but this is not used by the server.
+ #[serde(default = "is_true")]
+ pub test: bool,
}
impl Default for CodeLensSettings {
@@ -49,12 +57,24 @@ impl Default for CodeLensSettings {
implementations: false,
references: false,
references_all_functions: false,
+ test: true,
}
}
}
-fn is_true() -> bool {
- true
+#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct CodeLensSpecifierSettings {
+ /// Flag for providing test code lens on `Deno.test` statements. There is
+ /// also the `test_args` setting, but this is not used by the server.
+ #[serde(default = "is_true")]
+ pub test: bool,
+}
+
+impl Default for CodeLensSpecifierSettings {
+ fn default() -> Self {
+ Self { test: true }
+ }
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
@@ -109,9 +129,13 @@ impl Default for ImportCompletionSettings {
/// Deno language server specific settings that can be applied uniquely to a
/// specifier.
#[derive(Debug, Default, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
pub struct SpecifierSettings {
/// A flag that indicates if Deno is enabled for this specifier or not.
pub enable: bool,
+ /// Code lens specific settings for the resource.
+ #[serde(default)]
+ pub code_lens: CodeLensSpecifierSettings,
}
/// Deno language server specific settings that are applied to a workspace.
@@ -324,11 +348,21 @@ impl Config {
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
let settings = self.settings.read().unwrap();
- if let Some(specifier_settings) = settings.specifiers.get(specifier) {
- specifier_settings.1.enable
- } else {
- settings.workspace.enable
- }
+ settings
+ .specifiers
+ .get(specifier)
+ .map(|(_, s)| s.enable)
+ .unwrap_or_else(|| settings.workspace.enable)
+ }
+
+ pub fn specifier_code_lens_test(&self, specifier: &ModuleSpecifier) -> bool {
+ let settings = self.settings.read().unwrap();
+ let value = settings
+ .specifiers
+ .get(specifier)
+ .map(|(_, s)| s.code_lens.test)
+ .unwrap_or_else(|| settings.workspace.code_lens.test);
+ value
}
#[allow(clippy::redundant_closure_call)]
@@ -449,6 +483,7 @@ mod tests {
implementations: false,
references: false,
references_all_functions: false,
+ test: true,
},
internal_debug: false,
lint: false,
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 9e4e6af14..cd952b548 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -243,7 +243,10 @@ impl Inner {
// moment
/// Searches already cached assets and documents and returns its text
/// content. If not found, `None` is returned.
- fn get_text_content(&self, specifier: &ModuleSpecifier) -> Option<String> {
+ pub(crate) fn get_text_content(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<String> {
if specifier.scheme() == "asset" {
self
.assets
@@ -256,6 +259,17 @@ impl Inner {
}
}
+ pub(crate) fn get_media_type(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<MediaType> {
+ if specifier.scheme() == "asset" || self.documents.contains_key(specifier) {
+ Some(MediaType::from(specifier))
+ } else {
+ self.sources.get_media_type(specifier)
+ }
+ }
+
pub(crate) async fn get_navigation_tree(
&mut self,
specifier: &ModuleSpecifier,
@@ -1099,15 +1113,15 @@ impl Inner {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
if !self.documents.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
- || !self.config.get_workspace_settings().enabled_code_lens()
+ || !(self.config.get_workspace_settings().enabled_code_lens()
+ || self.config.specifier_code_lens_test(&specifier))
{
return Ok(None);
}
let mark = self.performance.mark("code_lens", Some(&params));
- let code_lenses = code_lens::tsc_code_lenses(&specifier, self)
- .await
- .map_err(|err| {
+ let code_lenses =
+ code_lens::collect(&specifier, self).await.map_err(|err| {
error!("Error getting code lenses for \"{}\": {}", specifier, err);
LspError::internal_error()
})?;
diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs
index e36de9ae3..0d203c7f8 100644
--- a/cli/tests/integration_tests_lsp.rs
+++ b/cli/tests/integration_tests_lsp.rs
@@ -45,7 +45,15 @@ where
let (id, method, _) = client.read_request::<Value>().unwrap();
assert_eq!(method, "workspace/configuration");
client
- .write_response(id, json!({ "enable": true }))
+ .write_response(
+ id,
+ json!({
+ "enable": true,
+ "codeLens": {
+ "test": true
+ }
+ }),
+ )
.unwrap();
let mut diagnostics = vec![];
@@ -1230,6 +1238,76 @@ fn lsp_code_lens_impl() {
}
#[test]
+fn lsp_code_lens_test() {
+ let mut client = init("initialize_params_code_lens_test.json");
+ did_open(
+ &mut client,
+ load_fixture("did_open_params_test_code_lens.json"),
+ );
+ let (maybe_res, maybe_err) = client
+ .write_request(
+ "textDocument/codeLens",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts"
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(
+ maybe_res,
+ Some(load_fixture("code_lens_response_test.json"))
+ );
+ shutdown(&mut client);
+}
+
+#[test]
+fn lsp_code_lens_test_disabled() {
+ let mut client = init("initialize_params_code_lens_test_disabled.json");
+ client
+ .write_notification(
+ "textDocument/didOpen",
+ load_fixture("did_open_params_test_code_lens.json"),
+ )
+ .unwrap();
+
+ let (id, method, _) = client.read_request::<Value>().unwrap();
+ assert_eq!(method, "workspace/configuration");
+ client
+ .write_response(
+ id,
+ json!({
+ "enable": true,
+ "codeLens": {
+ "test": false
+ }
+ }),
+ )
+ .unwrap();
+
+ let (method, _) = client.read_notification::<Value>().unwrap();
+ assert_eq!(method, "textDocument/publishDiagnostics");
+ let (method, _) = client.read_notification::<Value>().unwrap();
+ assert_eq!(method, "textDocument/publishDiagnostics");
+ let (method, _) = client.read_notification::<Value>().unwrap();
+ assert_eq!(method, "textDocument/publishDiagnostics");
+ let (maybe_res, maybe_err) = client
+ .write_request(
+ "textDocument/codeLens",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts"
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(maybe_res, Some(json!([])));
+ shutdown(&mut client);
+}
+
+#[test]
fn lsp_code_lens_non_doc_nav_tree() {
let mut client = init("initialize_params.json");
did_open(
diff --git a/cli/tests/lsp/code_lens_response_test.json b/cli/tests/lsp/code_lens_response_test.json
new file mode 100644
index 000000000..b2cb4588a
--- /dev/null
+++ b/cli/tests/lsp/code_lens_response_test.json
@@ -0,0 +1,162 @@
+[
+ {
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 5
+ },
+ "end": {
+ "line": 4,
+ "character": 9
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test a"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 5,
+ "character": 5
+ },
+ "end": {
+ "line": 5,
+ "character": 9
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test b"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 9,
+ "character": 0
+ },
+ "end": {
+ "line": 9,
+ "character": 4
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test c"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 13,
+ "character": 0
+ },
+ "end": {
+ "line": 13,
+ "character": 4
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test d"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 14,
+ "character": 0
+ },
+ "end": {
+ "line": 14,
+ "character": 5
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test e"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 18,
+ "character": 0
+ },
+ "end": {
+ "line": 18,
+ "character": 5
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test f"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 19,
+ "character": 0
+ },
+ "end": {
+ "line": 19,
+ "character": 5
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test g"
+ ]
+ }
+ },
+ {
+ "range": {
+ "start": {
+ "line": 23,
+ "character": 0
+ },
+ "end": {
+ "line": 23,
+ "character": 5
+ }
+ },
+ "command": {
+ "title": "▶︎ Run Test",
+ "command": "deno.test",
+ "arguments": [
+ "file:///a/file.ts",
+ "test h"
+ ]
+ }
+ }
+]
diff --git a/cli/tests/lsp/did_open_params_test_code_lens.json b/cli/tests/lsp/did_open_params_test_code_lens.json
new file mode 100644
index 000000000..dcb9e11f3
--- /dev/null
+++ b/cli/tests/lsp/did_open_params_test_code_lens.json
@@ -0,0 +1,8 @@
+{
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "const { test } = Deno;\nconst { test: test2 } = Deno;\nconst test3 = Deno.test;\n\nDeno.test(\"test a\", () => {});\nDeno.test({\n name: \"test b\",\n fn() {},\n});\ntest({\n name: \"test c\",\n fn() {},\n});\ntest(\"test d\", () => {});\ntest2({\n name: \"test e\",\n fn() {},\n});\ntest2(\"test f\", () => {});\ntest3({\n name: \"test g\",\n fn() {},\n});\ntest3(\"test h\", () => {});\n"
+ }
+}
diff --git a/cli/tests/lsp/initialize_params.json b/cli/tests/lsp/initialize_params.json
index 98ec53aa6..01b334cd5 100644
--- a/cli/tests/lsp/initialize_params.json
+++ b/cli/tests/lsp/initialize_params.json
@@ -9,7 +9,8 @@
"enable": true,
"codeLens": {
"implementations": true,
- "references": true
+ "references": true,
+ "test": true
},
"importMap": null,
"lint": true,
diff --git a/cli/tests/lsp/initialize_params_code_lens_test.json b/cli/tests/lsp/initialize_params_code_lens_test.json
new file mode 100644
index 000000000..792928462
--- /dev/null
+++ b/cli/tests/lsp/initialize_params_code_lens_test.json
@@ -0,0 +1,56 @@
+{
+ "processId": 0,
+ "clientInfo": {
+ "name": "test-harness",
+ "version": "1.0.0"
+ },
+ "rootUri": null,
+ "initializationOptions": {
+ "enable": true,
+ "importMap": null,
+ "lint": true,
+ "suggest": {
+ "autoImports": true,
+ "completeFunctionCalls": false,
+ "names": true,
+ "paths": true,
+ "imports": {
+ "hosts": {}
+ }
+ },
+ "unstable": false
+ },
+ "capabilities": {
+ "textDocument": {
+ "codeAction": {
+ "codeActionLiteralSupport": {
+ "codeActionKind": {
+ "valueSet": [
+ "quickfix"
+ ]
+ }
+ },
+ "isPreferredSupport": true,
+ "dataSupport": true,
+ "resolveSupport": {
+ "properties": [
+ "edit"
+ ]
+ }
+ },
+ "foldingRange": {
+ "lineFoldingOnly": true
+ },
+ "synchronization": {
+ "dynamicRegistration": true,
+ "willSave": true,
+ "willSaveWaitUntil": true,
+ "didSave": true
+ }
+ },
+ "workspace": {
+ "configuration": true,
+ "workspaceFolders": true
+ }
+ }
+}
diff --git a/cli/tests/lsp/initialize_params_code_lens_test_disabled.json b/cli/tests/lsp/initialize_params_code_lens_test_disabled.json
new file mode 100644
index 000000000..febec5695
--- /dev/null
+++ b/cli/tests/lsp/initialize_params_code_lens_test_disabled.json
@@ -0,0 +1,61 @@
+{
+ "processId": 0,
+ "clientInfo": {
+ "name": "test-harness",
+ "version": "1.0.0"
+ },
+ "rootUri": null,
+ "initializationOptions": {
+ "enable": true,
+ "codeLens": {
+ "implementations": true,
+ "references": true,
+ "test": false
+ },
+ "importMap": null,
+ "lint": true,
+ "suggest": {
+ "autoImports": true,
+ "completeFunctionCalls": false,
+ "names": true,
+ "paths": true,
+ "imports": {
+ "hosts": {}
+ }
+ },
+ "unstable": false
+ },
+ "capabilities": {
+ "textDocument": {
+ "codeAction": {
+ "codeActionLiteralSupport": {
+ "codeActionKind": {
+ "valueSet": [
+ "quickfix"
+ ]
+ }
+ },
+ "isPreferredSupport": true,
+ "dataSupport": true,
+ "resolveSupport": {
+ "properties": [
+ "edit"
+ ]
+ }
+ },
+ "foldingRange": {
+ "lineFoldingOnly": true
+ },
+ "synchronization": {
+ "dynamicRegistration": true,
+ "willSave": true,
+ "willSaveWaitUntil": true,
+ "didSave": true
+ }
+ },
+ "workspace": {
+ "configuration": true,
+ "workspaceFolders": true
+ }
+ }
+}