Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lsp): ts language service scopes #24345

Merged
merged 3 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cli/lsp/code_lens.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use crate::lsp::logging::lsp_warn;

use super::analysis::source_range_to_lsp_range;
use super::config::CodeLensSettings;
use super::language_server;
Expand Down Expand Up @@ -27,6 +29,7 @@ use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
use std::sync::Arc;
use tower_lsp::jsonrpc::Error as LspError;
use tower_lsp::lsp_types as lsp;

static ABSTRACT_MODIFIER: Lazy<Regex> = lazy_regex!(r"\babstract\b");
Expand Down Expand Up @@ -260,7 +263,11 @@ async fn resolve_implementation_code_lens(
data.specifier.clone(),
line_index.offset_tsc(code_lens.range.start)?,
)
.await?;
.await
.map_err(|err| {
lsp_warn!("{err}");
LspError::internal_error()
})?;
if let Some(implementations) = maybe_implementations {
let mut locations = Vec::new();
for implementation in implementations {
Expand Down Expand Up @@ -357,7 +364,11 @@ async fn resolve_references_code_lens(
data.specifier.clone(),
line_index.offset_tsc(code_lens.range.start)?,
)
.await?;
.await
.map_err(|err| {
lsp_warn!("Unable to find references: {err}");
LspError::internal_error()
})?;
let locations = get_locations(maybe_referenced_symbols, language_server)?;
let title = if locations.len() == 1 {
"1 reference".to_string()
Expand Down
9 changes: 0 additions & 9 deletions cli/lsp/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,6 @@ impl Default for LspTsConfig {
"esModuleInterop": true,
"experimentalDecorators": false,
"isolatedModules": true,
"jsx": "react",
"lib": ["deno.ns", "deno.window", "deno.unstable"],
"module": "esnext",
"moduleDetection": "force",
Expand Down Expand Up @@ -1569,16 +1568,10 @@ impl ConfigData {

#[derive(Clone, Debug, Default)]
pub struct ConfigTree {
first_folder: Option<ModuleSpecifier>,
scopes: Arc<BTreeMap<ModuleSpecifier, Arc<ConfigData>>>,
}

impl ConfigTree {
pub fn root_ts_config(&self) -> Arc<LspTsConfig> {
let root_data = self.first_folder.as_ref().and_then(|s| self.scopes.get(s));
root_data.map(|d| d.ts_config.clone()).unwrap_or_default()
}

pub fn scope_for_specifier(
&self,
specifier: &ModuleSpecifier,
Expand Down Expand Up @@ -1773,7 +1766,6 @@ impl ConfigTree {
);
}
}
self.first_folder.clone_from(&settings.first_folder);
self.scopes = Arc::new(scopes);
}

Expand All @@ -1790,7 +1782,6 @@ impl ConfigTree {
)
.await,
);
self.first_folder = Some(scope.clone());
self.scopes = Arc::new([(scope, data)].into_iter().collect());
}
}
Expand Down
49 changes: 34 additions & 15 deletions cli/lsp/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ impl AssetOrDocument {
}
}

pub fn file_referrer(&self) -> Option<&ModuleSpecifier> {
match self {
AssetOrDocument::Asset(_) => None,
AssetOrDocument::Document(doc) => doc.file_referrer(),
}
}

pub fn scope(&self) -> Option<&ModuleSpecifier> {
match self {
AssetOrDocument::Asset(_) => None,
AssetOrDocument::Document(doc) => doc.scope(),
}
}

pub fn maybe_semantic_tokens(&self) -> Option<lsp::SemanticTokens> {
match self {
AssetOrDocument::Asset(_) => None,
Expand Down Expand Up @@ -605,6 +619,13 @@ impl Document {
self.file_referrer.as_ref()
}

pub fn scope(&self) -> Option<&ModuleSpecifier> {
self
.file_referrer
.as_ref()
.and_then(|r| self.config.tree.scope_for_specifier(r))
}

pub fn content(&self) -> &Arc<str> {
&self.text
}
Expand Down Expand Up @@ -926,9 +947,9 @@ pub struct Documents {
/// The npm package requirements found in npm specifiers.
npm_reqs_by_scope:
Arc<BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>>,
/// Gets if any document had a node: specifier such that a @types/node package
/// should be injected.
has_injected_types_node_package: bool,
/// Config scopes that contain a node: specifier such that a @types/node
/// package should be injected.
scopes_with_node_specifier: Arc<HashSet<Option<ModuleSpecifier>>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little strange to see a hashmap containing an Option. Maybe we should bury this behaviour within a struct and make the public api of the struct better explain what's going on here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nvm, I see what this means. We may want to give Option<ModuleSpecifier> a name like ScopeKey or something?

}

impl Documents {
Expand Down Expand Up @@ -1122,10 +1143,10 @@ impl Documents {
self.npm_reqs_by_scope.clone()
}

/// Returns if a @types/node package was injected into the npm
/// resolver based on the state of the documents.
pub fn has_injected_types_node_package(&self) -> bool {
self.has_injected_types_node_package
pub fn scopes_with_node_specifier(
&self,
) -> &Arc<HashSet<Option<ModuleSpecifier>>> {
&self.scopes_with_node_specifier
}

/// Return a document for the specifier.
Expand Down Expand Up @@ -1346,20 +1367,18 @@ impl Documents {
/// document.
fn calculate_npm_reqs_if_dirty(&mut self) {
let mut npm_reqs_by_scope: BTreeMap<_, BTreeSet<_>> = Default::default();
let mut scopes_with_node_builtin_specifier = HashSet::new();
let mut scopes_with_specifier = HashSet::new();
let is_fs_docs_dirty = self.file_system_docs.set_dirty(false);
if !is_fs_docs_dirty && !self.dirty {
return;
}
let mut visit_doc = |doc: &Arc<Document>| {
let scope = doc
.file_referrer()
.and_then(|r| self.config.tree.scope_for_specifier(r));
let scope = doc.scope();
let reqs = npm_reqs_by_scope.entry(scope.cloned()).or_default();
for dependency in doc.dependencies().values() {
if let Some(dep) = dependency.get_code() {
if dep.scheme() == "node" {
scopes_with_node_builtin_specifier.insert(scope.cloned());
scopes_with_specifier.insert(scope.cloned());
}
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
reqs.insert(reference.into_inner().req);
Expand Down Expand Up @@ -1402,15 +1421,15 @@ impl Documents {
// Ensure a @types/node package exists when any module uses a node: specifier.
// Unlike on the command line, here we just add @types/node to the npm package
// requirements since this won't end up in the lockfile.
for scope in scopes_with_node_builtin_specifier {
let reqs = npm_reqs_by_scope.entry(scope).or_default();
for scope in &scopes_with_specifier {
let reqs = npm_reqs_by_scope.entry(scope.clone()).or_default();
if !reqs.iter().any(|r| r.name == "@types/node") {
self.has_injected_types_node_package = true;
reqs.insert(PackageReq::from_str("@types/node").unwrap());
}
}

self.npm_reqs_by_scope = Arc::new(npm_reqs_by_scope);
self.scopes_with_node_specifier = Arc::new(scopes_with_specifier);
self.dirty = false;
}

Expand Down
Loading