feat(lsp): jupyter notebook analysis (#20719)

This commit is contained in:
Nayeem Rahman 2023-09-29 20:44:59 +01:00 committed by GitHub
parent 61b91e10ad
commit 2d1af0cf51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 623 additions and 135 deletions

1
Cargo.lock generated
View file

@ -980,6 +980,7 @@ dependencies = [
"clap_complete", "clap_complete",
"clap_complete_fig", "clap_complete_fig",
"console_static_text", "console_static_text",
"dashmap 5.5.3",
"data-encoding", "data-encoding",
"data-url", "data-url",
"deno_ast", "deno_ast",

View file

@ -72,6 +72,7 @@ clap = { version = "=4.3.3", features = ["string"] }
clap_complete = "=4.3.1" clap_complete = "=4.3.1"
clap_complete_fig = "=4.3.1" clap_complete_fig = "=4.3.1"
console_static_text.workspace = true console_static_text.workspace = true
dashmap = "5.5.3"
data-encoding.workspace = true data-encoding.workspace = true
data-url.workspace = true data-url.workspace = true
dissimilar = "=1.0.4" dissimilar = "=1.0.4"

View file

@ -2,6 +2,7 @@
use super::client::Client; use super::client::Client;
use super::config::ConfigSnapshot; use super::config::ConfigSnapshot;
use super::documents::cell_to_file_specifier;
use super::documents::Documents; use super::documents::Documents;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::lsp_custom; use super::lsp_custom;
@ -364,11 +365,16 @@ fn get_local_completions(
current: &str, current: &str,
range: &lsp::Range, range: &lsp::Range,
) -> Option<Vec<lsp::CompletionItem>> { ) -> Option<Vec<lsp::CompletionItem>> {
let base = match cell_to_file_specifier(base) {
Some(s) => s,
None => base.clone(),
};
if base.scheme() != "file" { if base.scheme() != "file" {
return None; return None;
} }
let mut base_path = specifier_to_file_path(base).ok()?; let mut base_path = specifier_to_file_path(&base).ok()?;
base_path.pop(); base_path.pop();
let mut current_path = normalize_path(base_path.join(current)); let mut current_path = normalize_path(base_path.join(current));
// if the current text does not end in a `/` then we are still selecting on // if the current text does not end in a `/` then we are still selecting on
@ -388,10 +394,10 @@ fn get_local_completions(
let de = de.ok()?; let de = de.ok()?;
let label = de.path().file_name()?.to_string_lossy().to_string(); let label = de.path().file_name()?.to_string_lossy().to_string();
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?; let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
if &entry_specifier == base { if entry_specifier == base {
return None; return None;
} }
let full_text = relative_specifier(base, &entry_specifier)?; let full_text = relative_specifier(&base, &entry_specifier)?;
// this weeds out situations where we are browsing in the parent, but // this weeds out situations where we are browsing in the parent, but
// we want to filter out non-matches when the completion is manually // we want to filter out non-matches when the completion is manually
// invoked by the user, but still allows for things like `../src/../` // invoked by the user, but still allows for things like `../src/../`

View file

@ -692,10 +692,14 @@ impl WorkspaceSettings {
self.code_lens.implementations || self.code_lens.references self.code_lens.implementations || self.code_lens.references
} }
// TODO(nayeemrmn): Factor in out-of-band media type here.
pub fn language_settings_for_specifier( pub fn language_settings_for_specifier(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<&LanguageWorkspaceSettings> { ) -> Option<&LanguageWorkspaceSettings> {
if specifier.scheme() == "deno-notebook-cell" {
return Some(&self.typescript);
}
match MediaType::from_specifier(specifier) { match MediaType::from_specifier(specifier) {
MediaType::JavaScript MediaType::JavaScript
| MediaType::Jsx | MediaType::Jsx

View file

@ -16,7 +16,6 @@ use crate::cache::HttpCache;
use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::get_source_from_data_url; use crate::file_fetcher::get_source_from_data_url;
use crate::file_fetcher::map_content_type; use crate::file_fetcher::map_content_type;
use crate::file_fetcher::SUPPORTED_SCHEMES;
use crate::lsp::logging::lsp_warn; use crate::lsp::logging::lsp_warn;
use crate::npm::CliNpmRegistryApi; use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
@ -93,6 +92,15 @@ static TSX_HEADERS: Lazy<HashMap<String, String>> = Lazy::new(|| {
.collect() .collect()
}); });
pub const DOCUMENT_SCHEMES: [&str; 6] = [
"data",
"blob",
"file",
"http",
"https",
"deno-notebook-cell",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LanguageId { pub enum LanguageId {
JavaScript, JavaScript,
@ -254,6 +262,27 @@ impl AssetOrDocument {
} }
} }
/// Convert a `deno-notebook-cell:` specifier to a `file:` specifier.
/// ```rust
/// assert_eq!(
/// cell_to_file_specifier(
/// &Url::parse("deno-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
/// ),
/// Some(Url::parse("file:///path/to/file.ipynb#abc").unwrap()),
/// );
pub fn cell_to_file_specifier(specifier: &Url) -> Option<Url> {
if specifier.scheme() == "deno-notebook-cell" {
if let Ok(specifier) = ModuleSpecifier::parse(&format!(
"file://{}",
&specifier.as_str()
[url::quirks::internal_components(specifier).host_end as usize..],
)) {
return Some(specifier);
}
}
None
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct DocumentDependencies { struct DocumentDependencies {
deps: IndexMap<String, deno_graph::Dependency>, deps: IndexMap<String, deno_graph::Dependency>,
@ -270,10 +299,32 @@ impl DocumentDependencies {
} }
pub fn from_module(module: &deno_graph::EsmModule) -> Self { pub fn from_module(module: &deno_graph::EsmModule) -> Self {
Self { let mut deps = Self {
deps: module.dependencies.clone(), deps: module.dependencies.clone(),
maybe_types_dependency: module.maybe_types_dependency.clone(), maybe_types_dependency: module.maybe_types_dependency.clone(),
};
if module.specifier.scheme() == "deno-notebook-cell" {
for (_, dep) in &mut deps.deps {
if let Resolution::Ok(resolved) = &mut dep.maybe_code {
if let Some(specifier) = cell_to_file_specifier(&resolved.specifier) {
resolved.specifier = specifier;
}
}
if let Resolution::Ok(resolved) = &mut dep.maybe_type {
if let Some(specifier) = cell_to_file_specifier(&resolved.specifier) {
resolved.specifier = specifier;
}
}
}
if let Some(dep) = &mut deps.maybe_types_dependency {
if let Resolution::Ok(resolved) = &mut dep.dependency {
if let Some(specifier) = cell_to_file_specifier(&resolved.specifier) {
resolved.specifier = specifier;
}
}
}
} }
deps
} }
} }
@ -677,13 +728,11 @@ impl SpecifierResolver {
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<ModuleSpecifier> { ) -> Option<ModuleSpecifier> {
let scheme = specifier.scheme(); let scheme = specifier.scheme();
if !SUPPORTED_SCHEMES.contains(&scheme) { if !DOCUMENT_SCHEMES.contains(&scheme) {
return None; return None;
} }
if scheme == "data" || scheme == "blob" || scheme == "file" { if scheme == "http" || scheme == "https" {
Some(specifier.clone())
} else {
let mut redirects = self.redirects.lock(); let mut redirects = self.redirects.lock();
if let Some(specifier) = redirects.get(specifier) { if let Some(specifier) = redirects.get(specifier) {
Some(specifier.clone()) Some(specifier.clone())
@ -692,6 +741,8 @@ impl SpecifierResolver {
redirects.insert(specifier.clone(), redirect.clone()); redirects.insert(specifier.clone(), redirect.clone());
Some(redirect) Some(redirect)
} }
} else {
Some(specifier.clone())
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -7734,6 +7734,49 @@ fn lsp_diagnostics_refresh_dependents() {
assert_eq!(client.queue_len(), 0); assert_eq!(client.queue_len(), 0);
} }
#[test]
fn lsp_jupyter_diagnostics() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let mut client = context.new_lsp_command().build();
client.initialize_default();
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": "deno-notebook-cell:/a/file.ts#abc",
"languageId": "typescript",
"version": 1,
"text": "Deno.readTextFileSync(1234);",
},
}));
assert_eq!(
json!(diagnostics.all_messages()),
json!([
{
"uri": "deno-notebook-cell:/a/file.ts#abc",
"diagnostics": [
{
"range": {
"start": {
"line": 0,
"character": 22,
},
"end": {
"line": 0,
"character": 26,
},
},
"severity": 1,
"code": 2345,
"source": "deno-ts",
"message": "Argument of type 'number' is not assignable to parameter of type 'string | URL'.",
},
],
"version": 1,
},
])
);
client.shutdown();
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PerformanceAverage { pub struct PerformanceAverage {
@ -9504,7 +9547,7 @@ fn lsp_data_urls_with_jsx_compiler_option() {
"end": { "line": 1, "character": 1 } "end": { "line": 1, "character": 1 }
} }
}, { }, {
"uri": "deno:/5c42b5916c4a3fb55be33fdb0c3b1f438639420592d150fca1b6dc043c1df3d9/data_url.ts", "uri": "deno:/ed0224c51f7e2a845dfc0941ed6959675e5e3e3d2a39b127f0ff569c1ffda8d8/data_url.ts",
"range": { "range": {
"start": { "line": 0, "character": 7 }, "start": { "line": 0, "character": 7 },
"end": {"line": 0, "character": 14 }, "end": {"line": 0, "character": 14 },