2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2021-01-22 05:03:16 -05:00
|
|
|
|
2022-02-01 21:04:26 -05:00
|
|
|
use super::cache::calculate_fs_version;
|
2024-05-09 15:22:27 -04:00
|
|
|
use super::cache::LspCache;
|
2024-03-21 00:29:52 -04:00
|
|
|
use super::config::Config;
|
2024-04-26 16:39:33 -04:00
|
|
|
use super::resolver::LspResolver;
|
2024-11-15 09:40:32 -05:00
|
|
|
use super::resolver::ScopeDepInfo;
|
2024-11-13 10:10:09 -05:00
|
|
|
use super::resolver::SingleReferrerGraphResolver;
|
2024-04-19 21:00:03 -04:00
|
|
|
use super::testing::TestCollector;
|
|
|
|
use super::testing::TestModule;
|
2021-01-22 05:03:16 -05:00
|
|
|
use super::text::LineIndex;
|
2021-06-04 17:31:44 -04:00
|
|
|
use super::tsc;
|
2021-11-12 11:42:04 -05:00
|
|
|
use super::tsc::AssetDocument;
|
2021-01-22 05:03:16 -05:00
|
|
|
|
2024-04-24 17:43:34 -04:00
|
|
|
use crate::graph_util::CliJsrUrlProvider;
|
2021-10-28 19:56:01 -04:00
|
|
|
|
2024-04-09 18:12:55 -04:00
|
|
|
use dashmap::DashMap;
|
2024-04-19 21:00:03 -04:00
|
|
|
use deno_ast::swc::visit::VisitWith;
|
2021-09-07 10:39:32 -04:00
|
|
|
use deno_ast::MediaType;
|
2022-08-22 12:14:59 -04:00
|
|
|
use deno_ast::ParsedSource;
|
2021-10-28 19:56:01 -04:00
|
|
|
use deno_ast::SourceTextInfo;
|
2021-01-22 05:03:16 -05:00
|
|
|
use deno_core::error::custom_error;
|
|
|
|
use deno_core::error::AnyError;
|
2022-10-20 13:23:21 -04:00
|
|
|
use deno_core::futures::future;
|
2024-04-19 21:00:03 -04:00
|
|
|
use deno_core::futures::future::Shared;
|
2023-09-07 09:09:16 -04:00
|
|
|
use deno_core::futures::FutureExt;
|
2021-10-28 19:56:01 -04:00
|
|
|
use deno_core::parking_lot::Mutex;
|
2021-01-22 05:03:16 -05:00
|
|
|
use deno_core::ModuleSpecifier;
|
2023-02-09 22:00:23 -05:00
|
|
|
use deno_graph::Resolution;
|
2024-09-28 07:55:01 -04:00
|
|
|
use deno_path_util::url_to_file_path;
|
2023-04-21 21:02:46 -04:00
|
|
|
use deno_runtime::deno_node;
|
2024-03-04 10:48:23 -05:00
|
|
|
use deno_semver::jsr::JsrPackageReqReference;
|
2023-04-06 18:46:44 -04:00
|
|
|
use deno_semver::npm::NpmPackageReqReference;
|
2023-08-21 05:53:52 -04:00
|
|
|
use deno_semver::package::PackageReq;
|
2023-09-07 09:09:16 -04:00
|
|
|
use indexmap::IndexMap;
|
2024-06-11 16:06:43 -04:00
|
|
|
use indexmap::IndexSet;
|
2024-11-26 14:38:24 -05:00
|
|
|
use node_resolver::NodeResolutionKind;
|
|
|
|
use node_resolver::ResolutionMode;
|
2023-12-06 19:03:18 -05:00
|
|
|
use std::borrow::Cow;
|
2024-06-11 13:14:36 -04:00
|
|
|
use std::collections::BTreeMap;
|
2021-01-22 05:03:16 -05:00
|
|
|
use std::collections::HashMap;
|
2021-06-03 07:13:53 -04:00
|
|
|
use std::collections::HashSet;
|
2021-10-28 19:56:01 -04:00
|
|
|
use std::fs;
|
2024-04-19 21:00:03 -04:00
|
|
|
use std::future::Future;
|
2021-01-22 05:03:16 -05:00
|
|
|
use std::ops::Range;
|
2024-04-19 21:00:03 -04:00
|
|
|
use std::pin::Pin;
|
2021-06-02 06:29:58 -04:00
|
|
|
use std::str::FromStr;
|
2024-04-09 18:12:55 -04:00
|
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use std::sync::atomic::Ordering;
|
2021-09-07 10:39:32 -04:00
|
|
|
use std::sync::Arc;
|
2022-04-03 00:17:30 -04:00
|
|
|
use tower_lsp::lsp_types as lsp;
|
2021-10-28 19:56:01 -04:00
|
|
|
|
2023-11-24 17:35:33 -05:00
|
|
|
pub const DOCUMENT_SCHEMES: [&str; 5] =
|
|
|
|
["data", "blob", "file", "http", "https"];
|
2023-09-29 15:44:59 -04:00
|
|
|
|
2022-12-20 15:19:35 -05:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2022-03-23 09:54:22 -04:00
|
|
|
pub enum LanguageId {
|
2021-06-02 06:29:58 -04:00
|
|
|
JavaScript,
|
|
|
|
Jsx,
|
|
|
|
TypeScript,
|
|
|
|
Tsx,
|
|
|
|
Json,
|
|
|
|
JsonC,
|
|
|
|
Markdown,
|
2024-09-02 13:27:25 -04:00
|
|
|
Html,
|
|
|
|
Css,
|
|
|
|
Yaml,
|
2021-08-18 23:19:12 -04:00
|
|
|
Unknown,
|
2021-06-02 06:29:58 -04:00
|
|
|
}
|
|
|
|
|
2021-10-28 19:56:01 -04:00
|
|
|
impl LanguageId {
|
2022-12-20 15:19:35 -05:00
|
|
|
pub fn as_extension(&self) -> Option<&'static str> {
|
|
|
|
match self {
|
|
|
|
LanguageId::JavaScript => Some("js"),
|
|
|
|
LanguageId::Jsx => Some("jsx"),
|
|
|
|
LanguageId::TypeScript => Some("ts"),
|
|
|
|
LanguageId::Tsx => Some("tsx"),
|
|
|
|
LanguageId::Json => Some("json"),
|
|
|
|
LanguageId::JsonC => Some("jsonc"),
|
|
|
|
LanguageId::Markdown => Some("md"),
|
2024-09-02 13:27:25 -04:00
|
|
|
LanguageId::Html => Some("html"),
|
|
|
|
LanguageId::Css => Some("css"),
|
|
|
|
LanguageId::Yaml => Some("yaml"),
|
2022-12-20 15:19:35 -05:00
|
|
|
LanguageId::Unknown => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-15 17:50:52 -04:00
|
|
|
pub fn as_content_type(&self) -> Option<&'static str> {
|
2021-10-28 19:56:01 -04:00
|
|
|
match self {
|
2024-04-15 17:50:52 -04:00
|
|
|
LanguageId::JavaScript => Some("application/javascript"),
|
|
|
|
LanguageId::Jsx => Some("text/jsx"),
|
|
|
|
LanguageId::TypeScript => Some("application/typescript"),
|
|
|
|
LanguageId::Tsx => Some("text/tsx"),
|
|
|
|
LanguageId::Json | LanguageId::JsonC => Some("application/json"),
|
|
|
|
LanguageId::Markdown => Some("text/markdown"),
|
2024-09-02 13:27:25 -04:00
|
|
|
LanguageId::Html => Some("text/html"),
|
|
|
|
LanguageId::Css => Some("text/css"),
|
|
|
|
LanguageId::Yaml => Some("application/yaml"),
|
2024-04-15 17:50:52 -04:00
|
|
|
LanguageId::Unknown => None,
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_diagnosable(&self) -> bool {
|
|
|
|
matches!(
|
|
|
|
self,
|
|
|
|
Self::JavaScript | Self::Jsx | Self::TypeScript | Self::Tsx
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 06:29:58 -04:00
|
|
|
impl FromStr for LanguageId {
|
|
|
|
type Err = AnyError;
|
|
|
|
|
2021-08-18 23:19:12 -04:00
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
2021-06-02 06:29:58 -04:00
|
|
|
match s {
|
|
|
|
"javascript" => Ok(Self::JavaScript),
|
2021-10-28 19:56:01 -04:00
|
|
|
"javascriptreact" | "jsx" => Ok(Self::Jsx),
|
2021-06-02 06:29:58 -04:00
|
|
|
"typescript" => Ok(Self::TypeScript),
|
2021-10-28 19:56:01 -04:00
|
|
|
"typescriptreact" | "tsx" => Ok(Self::Tsx),
|
2021-06-02 06:29:58 -04:00
|
|
|
"json" => Ok(Self::Json),
|
|
|
|
"jsonc" => Ok(Self::JsonC),
|
|
|
|
"markdown" => Ok(Self::Markdown),
|
2024-09-02 13:27:25 -04:00
|
|
|
"html" => Ok(Self::Html),
|
|
|
|
"css" => Ok(Self::Css),
|
|
|
|
"yaml" => Ok(Self::Yaml),
|
2021-08-18 23:19:12 -04:00
|
|
|
_ => Ok(Self::Unknown),
|
2021-06-02 06:29:58 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
enum IndexValid {
|
|
|
|
All,
|
|
|
|
UpTo(u32),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IndexValid {
|
|
|
|
fn covers(&self, line: u32) -> bool {
|
|
|
|
match *self {
|
|
|
|
IndexValid::UpTo(to) => to > line,
|
|
|
|
IndexValid::All => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 11:42:04 -05:00
|
|
|
#[derive(Debug, Clone)]
|
2022-03-02 16:06:38 -05:00
|
|
|
pub enum AssetOrDocument {
|
2024-04-06 10:36:43 -04:00
|
|
|
Document(Arc<Document>),
|
2021-11-12 11:42:04 -05:00
|
|
|
Asset(AssetDocument),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AssetOrDocument {
|
2024-04-06 10:36:43 -04:00
|
|
|
pub fn document(&self) -> Option<&Arc<Document>> {
|
2021-11-12 11:42:04 -05:00
|
|
|
match self {
|
|
|
|
AssetOrDocument::Asset(_) => None,
|
|
|
|
AssetOrDocument::Document(doc) => Some(doc),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 18:47:01 -04:00
|
|
|
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 {
|
2024-07-22 10:06:08 -04:00
|
|
|
AssetOrDocument::Asset(asset_doc) => Some(asset_doc.specifier()),
|
2024-06-26 18:47:01 -04:00
|
|
|
AssetOrDocument::Document(doc) => doc.scope(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
perf(lsp): Cache semantic tokens for open documents (#23799)
VScode will typically send a `textDocument/semanticTokens/full` request
followed by `textDocument/semanticTokens/range`, and occassionally
request semantic tokens even when we know nothing has changed. Semantic
tokens also get refreshed on each change. Computing semantic tokens is
relatively heavy in TSC, so we should avoid it as much as possible.
Caches the semantic tokens for open documents, to avoid making TSC do
unnecessary work. Results in a noticeable improvement in local
benchmarking
before:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 383ms)
- Big Document/Several Edits
(5 runs, mean: 1079ms)
- Find/Replace
(10 runs, mean: 59ms)
- Code Lens
(10 runs, mean: 440ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 9921ms)
<- End benchmarking lsp
```
after:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 395ms)
- Big Document/Several Edits
(5 runs, mean: 1024ms)
- Find/Replace
(10 runs, mean: 56ms)
- Code Lens
(10 runs, mean: 438ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 8927ms)
<- End benchmarking lsp
```
2024-05-14 21:51:48 -04:00
|
|
|
pub fn maybe_semantic_tokens(&self) -> Option<lsp::SemanticTokens> {
|
|
|
|
match self {
|
|
|
|
AssetOrDocument::Asset(_) => None,
|
|
|
|
AssetOrDocument::Document(d) => d
|
|
|
|
.open_data
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|d| d.maybe_semantic_tokens.lock().clone()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-20 16:40:55 -04:00
|
|
|
pub fn text(&self) -> Arc<str> {
|
2021-11-12 11:42:04 -05:00
|
|
|
match self {
|
|
|
|
AssetOrDocument::Asset(a) => a.text(),
|
2024-06-05 11:04:16 -04:00
|
|
|
AssetOrDocument::Document(d) => d.text.clone(),
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn line_index(&self) -> Arc<LineIndex> {
|
|
|
|
match self {
|
|
|
|
AssetOrDocument::Asset(a) => a.line_index(),
|
|
|
|
AssetOrDocument::Document(d) => d.line_index(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_navigation_tree(&self) -> Option<Arc<tsc::NavigationTree>> {
|
|
|
|
match self {
|
|
|
|
AssetOrDocument::Asset(a) => a.maybe_navigation_tree(),
|
|
|
|
AssetOrDocument::Document(d) => d.maybe_navigation_tree(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-15 14:41:37 -04:00
|
|
|
pub fn media_type(&self) -> MediaType {
|
|
|
|
match self {
|
|
|
|
AssetOrDocument::Asset(_) => MediaType::TypeScript, // assets are always TypeScript
|
|
|
|
AssetOrDocument::Document(d) => d.media_type(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 11:42:04 -05:00
|
|
|
pub fn get_maybe_dependency(
|
|
|
|
&self,
|
|
|
|
position: &lsp::Position,
|
|
|
|
) -> Option<(String, deno_graph::Dependency, deno_graph::Range)> {
|
|
|
|
self
|
|
|
|
.document()
|
2022-02-24 20:03:12 -05:00
|
|
|
.and_then(|d| d.get_maybe_dependency(position))
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_parsed_source(
|
|
|
|
&self,
|
2024-06-05 11:04:16 -04:00
|
|
|
) -> Option<&Result<deno_ast::ParsedSource, deno_ast::ParseDiagnostic>> {
|
2022-02-24 20:03:12 -05:00
|
|
|
self.document().and_then(|d| d.maybe_parsed_source())
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
pub fn document_lsp_version(&self) -> Option<i32> {
|
2022-02-24 20:03:12 -05:00
|
|
|
self.document().and_then(|d| d.maybe_lsp_version())
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-31 22:15:22 -05:00
|
|
|
type ModuleResult = Result<deno_graph::JsModule, deno_graph::ModuleGraphError>;
|
2024-02-08 20:40:26 -05:00
|
|
|
type ParsedSourceResult = Result<ParsedSource, deno_ast::ParseDiagnostic>;
|
2024-04-19 21:00:03 -04:00
|
|
|
type TestModuleFut =
|
|
|
|
Shared<Pin<Box<dyn Future<Output = Option<Arc<TestModule>>> + Send>>>;
|
|
|
|
|
|
|
|
fn media_type_is_diagnosable(media_type: MediaType) -> bool {
|
|
|
|
matches!(
|
|
|
|
media_type,
|
|
|
|
MediaType::JavaScript
|
|
|
|
| MediaType::Jsx
|
|
|
|
| MediaType::Mjs
|
|
|
|
| MediaType::Cjs
|
|
|
|
| MediaType::TypeScript
|
|
|
|
| MediaType::Tsx
|
|
|
|
| MediaType::Mts
|
|
|
|
| MediaType::Cts
|
|
|
|
| MediaType::Dts
|
|
|
|
| MediaType::Dmts
|
|
|
|
| MediaType::Dcts
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_maybe_test_module_fut(
|
|
|
|
maybe_parsed_source: Option<&ParsedSourceResult>,
|
|
|
|
config: &Config,
|
|
|
|
) -> Option<TestModuleFut> {
|
2024-05-29 16:31:09 -04:00
|
|
|
if !config.testing_api_capable() {
|
2024-04-19 21:00:03 -04:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let parsed_source = maybe_parsed_source?.as_ref().ok()?.clone();
|
|
|
|
let specifier = parsed_source.specifier();
|
|
|
|
if specifier.scheme() != "file" {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
if !media_type_is_diagnosable(parsed_source.media_type()) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
if !config.specifier_enabled_for_test(specifier) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let handle = tokio::task::spawn_blocking(move || {
|
|
|
|
let mut collector = TestCollector::new(
|
|
|
|
parsed_source.specifier().clone(),
|
2024-06-05 11:04:16 -04:00
|
|
|
parsed_source.text_info_lazy().clone(),
|
2024-04-19 21:00:03 -04:00
|
|
|
);
|
2024-11-01 12:27:00 -04:00
|
|
|
parsed_source.program().visit_with(&mut collector);
|
2024-04-19 21:00:03 -04:00
|
|
|
Arc::new(collector.take())
|
|
|
|
})
|
|
|
|
.map(Result::ok)
|
|
|
|
.boxed()
|
|
|
|
.shared();
|
|
|
|
Some(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct DocumentOpenData {
|
2024-04-22 14:24:00 -04:00
|
|
|
lsp_version: i32,
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_parsed_source: Option<ParsedSourceResult>,
|
perf(lsp): Cache semantic tokens for open documents (#23799)
VScode will typically send a `textDocument/semanticTokens/full` request
followed by `textDocument/semanticTokens/range`, and occassionally
request semantic tokens even when we know nothing has changed. Semantic
tokens also get refreshed on each change. Computing semantic tokens is
relatively heavy in TSC, so we should avoid it as much as possible.
Caches the semantic tokens for open documents, to avoid making TSC do
unnecessary work. Results in a noticeable improvement in local
benchmarking
before:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 383ms)
- Big Document/Several Edits
(5 runs, mean: 1079ms)
- Find/Replace
(10 runs, mean: 59ms)
- Code Lens
(10 runs, mean: 440ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 9921ms)
<- End benchmarking lsp
```
after:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 395ms)
- Big Document/Several Edits
(5 runs, mean: 1024ms)
- Find/Replace
(10 runs, mean: 56ms)
- Code Lens
(10 runs, mean: 438ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 8927ms)
<- End benchmarking lsp
```
2024-05-14 21:51:48 -04:00
|
|
|
maybe_semantic_tokens: Arc<Mutex<Option<lsp::SemanticTokens>>>,
|
2024-04-19 21:00:03 -04:00
|
|
|
}
|
2022-08-22 12:14:59 -04:00
|
|
|
|
2022-12-19 20:22:17 -05:00
|
|
|
#[derive(Debug)]
|
2024-04-06 10:36:43 -04:00
|
|
|
pub struct Document {
|
2022-12-19 20:22:17 -05:00
|
|
|
/// Contains the last-known-good set of dependencies from parsing the module.
|
2024-04-22 18:55:20 -04:00
|
|
|
config: Arc<Config>,
|
2024-04-18 20:51:16 -04:00
|
|
|
dependencies: Arc<IndexMap<String, deno_graph::Dependency>>,
|
2024-11-13 10:10:09 -05:00
|
|
|
/// If this is maybe a CJS script and maybe not an ES module.
|
|
|
|
is_script: Option<bool>,
|
2024-05-23 12:31:56 -04:00
|
|
|
// TODO(nayeemrmn): This is unused, use it for scope attribution for remote
|
|
|
|
// modules.
|
|
|
|
file_referrer: Option<ModuleSpecifier>,
|
2024-04-18 20:51:16 -04:00
|
|
|
maybe_types_dependency: Option<Arc<deno_graph::TypesDependency>>,
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_fs_version: Option<String>,
|
2021-10-28 19:56:01 -04:00
|
|
|
line_index: Arc<LineIndex>,
|
2023-01-28 10:18:32 -05:00
|
|
|
maybe_headers: Option<HashMap<String, String>>,
|
2021-10-28 19:56:01 -04:00
|
|
|
maybe_language_id: Option<LanguageId>,
|
2024-04-22 14:24:00 -04:00
|
|
|
/// This is cached in a mutex so `workspace/symbol` and
|
|
|
|
/// `textDocument/codeLens` requests don't require a write lock.
|
2022-12-19 20:22:17 -05:00
|
|
|
maybe_navigation_tree: Mutex<Option<Arc<tsc::NavigationTree>>>,
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut: Option<TestModuleFut>,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type: MediaType,
|
2024-04-19 21:00:03 -04:00
|
|
|
/// Present if and only if this is an open document.
|
|
|
|
open_data: Option<DocumentOpenData>,
|
2024-11-27 09:50:38 -05:00
|
|
|
resolution_mode: ResolutionMode,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: Arc<LspResolver>,
|
2021-03-24 20:13:37 -04:00
|
|
|
specifier: ModuleSpecifier,
|
2024-06-05 11:04:16 -04:00
|
|
|
text: Arc<str>,
|
|
|
|
text_info_cell: once_cell::sync::OnceCell<SourceTextInfo>,
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2021-10-28 19:56:01 -04:00
|
|
|
impl Document {
|
2024-04-22 14:24:00 -04:00
|
|
|
/// Open documents should have `maybe_lsp_version.is_some()`.
|
2024-04-19 21:00:03 -04:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2021-10-28 19:56:01 -04:00
|
|
|
fn new(
|
2021-06-02 06:29:58 -04:00
|
|
|
specifier: ModuleSpecifier,
|
2024-06-05 11:04:16 -04:00
|
|
|
text: Arc<str>,
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_lsp_version: Option<i32>,
|
|
|
|
maybe_language_id: Option<LanguageId>,
|
2023-01-28 10:18:32 -05:00
|
|
|
maybe_headers: Option<HashMap<String, String>>,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: Arc<LspResolver>,
|
2024-04-22 18:55:20 -04:00
|
|
|
config: Arc<Config>,
|
2024-05-09 15:22:27 -04:00
|
|
|
cache: &Arc<LspCache>,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<ModuleSpecifier>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Arc<Self> {
|
2024-06-17 16:54:23 -04:00
|
|
|
let file_referrer = Some(&specifier)
|
2024-06-28 13:18:21 -04:00
|
|
|
.filter(|s| cache.is_valid_file_referrer(s))
|
2024-06-17 16:54:23 -04:00
|
|
|
.cloned()
|
|
|
|
.or(file_referrer);
|
2024-11-01 12:27:00 -04:00
|
|
|
let media_type =
|
|
|
|
resolve_media_type(&specifier, maybe_headers.as_ref(), maybe_language_id);
|
2024-11-27 09:50:38 -05:00
|
|
|
let (maybe_parsed_source, maybe_module, resolution_mode) =
|
2024-04-22 14:24:00 -04:00
|
|
|
if media_type_is_diagnosable(media_type) {
|
|
|
|
parse_and_analyze_module(
|
2024-05-28 14:58:43 -04:00
|
|
|
specifier.clone(),
|
2024-06-05 11:04:16 -04:00
|
|
|
text.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_headers.as_ref(),
|
|
|
|
media_type,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer.as_ref(),
|
2024-04-22 18:55:20 -04:00
|
|
|
&resolver,
|
2024-04-22 14:24:00 -04:00
|
|
|
)
|
|
|
|
} else {
|
2024-11-27 09:50:38 -05:00
|
|
|
(None, None, ResolutionMode::Import)
|
2024-04-22 14:24:00 -04:00
|
|
|
};
|
2024-04-18 20:51:16 -04:00
|
|
|
let maybe_module = maybe_module.and_then(Result::ok);
|
|
|
|
let dependencies = maybe_module
|
|
|
|
.as_ref()
|
|
|
|
.map(|m| Arc::new(m.dependencies.clone()))
|
|
|
|
.unwrap_or_default();
|
|
|
|
let maybe_types_dependency = maybe_module
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|m| Some(Arc::new(m.maybe_types_dependency.clone()?)));
|
2024-06-05 11:04:16 -04:00
|
|
|
let line_index = Arc::new(LineIndex::new(text.as_ref()));
|
2024-04-19 21:00:03 -04:00
|
|
|
let maybe_test_module_fut =
|
2024-04-22 18:55:20 -04:00
|
|
|
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config);
|
2024-04-22 14:24:00 -04:00
|
|
|
Arc::new(Self {
|
2024-04-22 18:55:20 -04:00
|
|
|
config,
|
2021-11-16 17:23:25 -05:00
|
|
|
dependencies,
|
2024-06-17 16:54:23 -04:00
|
|
|
maybe_fs_version: calculate_fs_version(
|
|
|
|
cache,
|
|
|
|
&specifier,
|
|
|
|
file_referrer.as_ref(),
|
|
|
|
),
|
|
|
|
file_referrer,
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script: maybe_module.as_ref().map(|m| m.is_script),
|
2024-04-18 20:51:16 -04:00
|
|
|
maybe_types_dependency,
|
2021-10-28 19:56:01 -04:00
|
|
|
line_index,
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_language_id,
|
2023-01-28 10:18:32 -05:00
|
|
|
maybe_headers,
|
2022-12-19 20:22:17 -05:00
|
|
|
maybe_navigation_tree: Mutex::new(None),
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type,
|
2024-04-22 14:24:00 -04:00
|
|
|
open_data: maybe_lsp_version.map(|v| DocumentOpenData {
|
|
|
|
lsp_version: v,
|
|
|
|
maybe_parsed_source,
|
perf(lsp): Cache semantic tokens for open documents (#23799)
VScode will typically send a `textDocument/semanticTokens/full` request
followed by `textDocument/semanticTokens/range`, and occassionally
request semantic tokens even when we know nothing has changed. Semantic
tokens also get refreshed on each change. Computing semantic tokens is
relatively heavy in TSC, so we should avoid it as much as possible.
Caches the semantic tokens for open documents, to avoid making TSC do
unnecessary work. Results in a noticeable improvement in local
benchmarking
before:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 383ms)
- Big Document/Several Edits
(5 runs, mean: 1079ms)
- Find/Replace
(10 runs, mean: 59ms)
- Code Lens
(10 runs, mean: 440ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 9921ms)
<- End benchmarking lsp
```
after:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 395ms)
- Big Document/Several Edits
(5 runs, mean: 1024ms)
- Find/Replace
(10 runs, mean: 56ms)
- Code Lens
(10 runs, mean: 438ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 8927ms)
<- End benchmarking lsp
```
2024-05-14 21:51:48 -04:00
|
|
|
maybe_semantic_tokens: Default::default(),
|
2024-04-22 14:24:00 -04:00
|
|
|
}),
|
2024-11-27 09:50:38 -05:00
|
|
|
resolution_mode,
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver,
|
2021-03-24 20:13:37 -04:00
|
|
|
specifier,
|
2024-06-05 11:04:16 -04:00
|
|
|
text,
|
|
|
|
text_info_cell: once_cell::sync::OnceCell::new(),
|
2024-04-06 10:36:43 -04:00
|
|
|
})
|
2021-03-24 20:13:37 -04:00
|
|
|
}
|
|
|
|
|
2024-04-22 18:55:20 -04:00
|
|
|
fn with_new_config(
|
2023-01-28 10:18:32 -05:00
|
|
|
&self,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: Arc<LspResolver>,
|
2024-04-22 18:55:20 -04:00
|
|
|
config: Arc<Config>,
|
2024-04-22 14:24:00 -04:00
|
|
|
) -> Arc<Self> {
|
2024-04-15 17:50:52 -04:00
|
|
|
let media_type = resolve_media_type(
|
|
|
|
&self.specifier,
|
|
|
|
self.maybe_headers.as_ref(),
|
|
|
|
self.maybe_language_id,
|
|
|
|
);
|
2024-04-18 20:51:16 -04:00
|
|
|
let dependencies;
|
|
|
|
let maybe_types_dependency;
|
|
|
|
let maybe_parsed_source;
|
2024-11-27 09:50:38 -05:00
|
|
|
let found_resolution_mode;
|
2024-11-13 10:10:09 -05:00
|
|
|
let is_script;
|
2024-04-19 21:00:03 -04:00
|
|
|
let maybe_test_module_fut;
|
2024-04-18 20:51:16 -04:00
|
|
|
if media_type != self.media_type {
|
2024-06-05 11:04:16 -04:00
|
|
|
let parsed_source_result =
|
|
|
|
parse_source(self.specifier.clone(), self.text.clone(), media_type);
|
2024-11-27 09:50:38 -05:00
|
|
|
let (maybe_module_result, resolution_mode) = analyze_module(
|
2024-05-28 14:58:43 -04:00
|
|
|
self.specifier.clone(),
|
2024-04-18 20:51:16 -04:00
|
|
|
&parsed_source_result,
|
|
|
|
self.maybe_headers.as_ref(),
|
2024-05-23 12:31:56 -04:00
|
|
|
self.file_referrer.as_ref(),
|
2024-04-22 18:55:20 -04:00
|
|
|
&resolver,
|
2024-11-27 09:50:38 -05:00
|
|
|
);
|
|
|
|
let maybe_module = maybe_module_result.ok();
|
2024-04-18 20:51:16 -04:00
|
|
|
dependencies = maybe_module
|
|
|
|
.as_ref()
|
|
|
|
.map(|m| Arc::new(m.dependencies.clone()))
|
|
|
|
.unwrap_or_default();
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script = maybe_module.as_ref().map(|m| m.is_script);
|
2024-04-18 20:51:16 -04:00
|
|
|
maybe_types_dependency = maybe_module
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|m| Some(Arc::new(m.maybe_types_dependency.clone()?)));
|
|
|
|
maybe_parsed_source = Some(parsed_source_result);
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut =
|
2024-04-22 18:55:20 -04:00
|
|
|
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config);
|
2024-11-27 09:50:38 -05:00
|
|
|
found_resolution_mode = resolution_mode;
|
2024-04-18 20:51:16 -04:00
|
|
|
} else {
|
2024-11-13 10:10:09 -05:00
|
|
|
let cli_resolver = resolver.as_cli_resolver(self.file_referrer.as_ref());
|
2024-11-27 09:50:38 -05:00
|
|
|
let is_cjs_resolver =
|
|
|
|
resolver.as_is_cjs_resolver(self.file_referrer.as_ref());
|
2024-05-23 12:31:56 -04:00
|
|
|
let npm_resolver =
|
2024-06-05 11:04:16 -04:00
|
|
|
resolver.create_graph_npm_resolver(self.file_referrer.as_ref());
|
2024-11-13 10:10:09 -05:00
|
|
|
let config_data = resolver.as_config_data(self.file_referrer.as_ref());
|
|
|
|
let jsx_import_source_config =
|
|
|
|
config_data.and_then(|d| d.maybe_jsx_import_source_config());
|
2024-11-27 09:50:38 -05:00
|
|
|
found_resolution_mode = is_cjs_resolver
|
|
|
|
.get_lsp_resolution_mode(&self.specifier, self.is_script);
|
2024-11-13 10:10:09 -05:00
|
|
|
let resolver = SingleReferrerGraphResolver {
|
|
|
|
valid_referrer: &self.specifier,
|
2024-11-27 09:50:38 -05:00
|
|
|
module_resolution_mode: found_resolution_mode,
|
2024-11-13 10:10:09 -05:00
|
|
|
cli_resolver,
|
|
|
|
jsx_import_source_config: jsx_import_source_config.as_ref(),
|
|
|
|
};
|
2024-04-18 20:51:16 -04:00
|
|
|
dependencies = Arc::new(
|
|
|
|
self
|
|
|
|
.dependencies
|
|
|
|
.iter()
|
|
|
|
.map(|(s, d)| {
|
|
|
|
(
|
|
|
|
s.clone(),
|
2024-04-24 17:43:34 -04:00
|
|
|
d.with_new_resolver(
|
|
|
|
s,
|
|
|
|
&CliJsrUrlProvider,
|
2024-11-13 10:10:09 -05:00
|
|
|
Some(&resolver),
|
2024-06-05 11:04:16 -04:00
|
|
|
Some(&npm_resolver),
|
2024-04-24 17:43:34 -04:00
|
|
|
),
|
2024-04-18 20:51:16 -04:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
);
|
|
|
|
maybe_types_dependency = self.maybe_types_dependency.as_ref().map(|d| {
|
2024-04-24 17:43:34 -04:00
|
|
|
Arc::new(d.with_new_resolver(
|
|
|
|
&CliJsrUrlProvider,
|
2024-11-13 10:10:09 -05:00
|
|
|
Some(&resolver),
|
2024-06-05 11:04:16 -04:00
|
|
|
Some(&npm_resolver),
|
2024-04-24 17:43:34 -04:00
|
|
|
))
|
2024-04-18 20:51:16 -04:00
|
|
|
});
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script = self.is_script;
|
2024-06-05 11:04:16 -04:00
|
|
|
maybe_parsed_source = self.maybe_parsed_source().cloned();
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut = self
|
|
|
|
.maybe_test_module_fut
|
|
|
|
.clone()
|
|
|
|
.filter(|_| config.specifier_enabled_for_test(&self.specifier));
|
2024-04-15 17:50:52 -04:00
|
|
|
}
|
2024-04-22 14:24:00 -04:00
|
|
|
Arc::new(Self {
|
2024-04-22 18:55:20 -04:00
|
|
|
config,
|
2023-01-28 10:18:32 -05:00
|
|
|
// updated properties
|
|
|
|
dependencies,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: self.file_referrer.clone(),
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script,
|
2024-04-18 20:51:16 -04:00
|
|
|
maybe_types_dependency,
|
2023-01-28 10:18:32 -05:00
|
|
|
maybe_navigation_tree: Mutex::new(None),
|
|
|
|
// maintain - this should all be copies/clones
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_fs_version: self.maybe_fs_version.clone(),
|
2024-04-06 10:36:43 -04:00
|
|
|
line_index: self.line_index.clone(),
|
|
|
|
maybe_headers: self.maybe_headers.clone(),
|
|
|
|
maybe_language_id: self.maybe_language_id,
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type,
|
2024-11-27 09:50:38 -05:00
|
|
|
resolution_mode: found_resolution_mode,
|
2024-04-22 14:24:00 -04:00
|
|
|
open_data: self.open_data.as_ref().map(|d| DocumentOpenData {
|
|
|
|
lsp_version: d.lsp_version,
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_parsed_source,
|
perf(lsp): Cache semantic tokens for open documents (#23799)
VScode will typically send a `textDocument/semanticTokens/full` request
followed by `textDocument/semanticTokens/range`, and occassionally
request semantic tokens even when we know nothing has changed. Semantic
tokens also get refreshed on each change. Computing semantic tokens is
relatively heavy in TSC, so we should avoid it as much as possible.
Caches the semantic tokens for open documents, to avoid making TSC do
unnecessary work. Results in a noticeable improvement in local
benchmarking
before:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 383ms)
- Big Document/Several Edits
(5 runs, mean: 1079ms)
- Find/Replace
(10 runs, mean: 59ms)
- Code Lens
(10 runs, mean: 440ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 9921ms)
<- End benchmarking lsp
```
after:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 395ms)
- Big Document/Several Edits
(5 runs, mean: 1024ms)
- Find/Replace
(10 runs, mean: 56ms)
- Code Lens
(10 runs, mean: 438ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 8927ms)
<- End benchmarking lsp
```
2024-05-14 21:51:48 -04:00
|
|
|
// reset semantic tokens
|
|
|
|
maybe_semantic_tokens: Default::default(),
|
2024-04-19 21:00:03 -04:00
|
|
|
}),
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver,
|
2024-04-06 10:36:43 -04:00
|
|
|
specifier: self.specifier.clone(),
|
2024-06-05 11:04:16 -04:00
|
|
|
text: self.text.clone(),
|
|
|
|
text_info_cell: once_cell::sync::OnceCell::new(),
|
2024-04-06 10:36:43 -04:00
|
|
|
})
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn with_change(
|
|
|
|
&self,
|
2021-10-28 19:56:01 -04:00
|
|
|
version: i32,
|
|
|
|
changes: Vec<lsp::TextDocumentContentChangeEvent>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Result<Arc<Self>, AnyError> {
|
2024-06-05 11:04:16 -04:00
|
|
|
let mut content = self.text.to_string();
|
2024-04-06 10:36:43 -04:00
|
|
|
let mut line_index = self.line_index.clone();
|
2021-01-22 05:03:16 -05:00
|
|
|
let mut index_valid = IndexValid::All;
|
2021-10-28 19:56:01 -04:00
|
|
|
for change in changes {
|
2021-01-22 05:03:16 -05:00
|
|
|
if let Some(range) = change.range {
|
|
|
|
if !index_valid.covers(range.start.line) {
|
2021-10-28 19:56:01 -04:00
|
|
|
line_index = Arc::new(LineIndex::new(&content));
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
index_valid = IndexValid::UpTo(range.start.line);
|
|
|
|
let range = line_index.get_text_range(range)?;
|
|
|
|
content.replace_range(Range::<usize>::from(range), &change.text);
|
|
|
|
} else {
|
2021-09-07 10:39:32 -04:00
|
|
|
content = change.text;
|
2021-01-22 05:03:16 -05:00
|
|
|
index_valid = IndexValid::UpTo(0);
|
|
|
|
}
|
|
|
|
}
|
2024-06-05 11:04:16 -04:00
|
|
|
let text: Arc<str> = content.into();
|
2024-04-15 17:50:52 -04:00
|
|
|
let media_type = self.media_type;
|
2024-11-27 09:50:38 -05:00
|
|
|
let (maybe_parsed_source, maybe_module, resolution_mode) = if self
|
2021-10-28 19:56:01 -04:00
|
|
|
.maybe_language_id
|
|
|
|
.as_ref()
|
|
|
|
.map(|li| li.is_diagnosable())
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2023-01-28 10:18:32 -05:00
|
|
|
parse_and_analyze_module(
|
2024-05-28 14:58:43 -04:00
|
|
|
self.specifier.clone(),
|
2024-06-05 11:04:16 -04:00
|
|
|
text.clone(),
|
2024-04-15 17:50:52 -04:00
|
|
|
self.maybe_headers.as_ref(),
|
|
|
|
media_type,
|
2024-05-23 12:31:56 -04:00
|
|
|
self.file_referrer.as_ref(),
|
2024-04-22 18:55:20 -04:00
|
|
|
self.resolver.as_ref(),
|
2022-08-22 12:14:59 -04:00
|
|
|
)
|
2021-10-28 19:56:01 -04:00
|
|
|
} else {
|
2024-11-27 09:50:38 -05:00
|
|
|
(None, None, ResolutionMode::Import)
|
2021-11-12 11:42:04 -05:00
|
|
|
};
|
2024-04-18 20:51:16 -04:00
|
|
|
let maybe_module = maybe_module.and_then(Result::ok);
|
|
|
|
let dependencies = maybe_module
|
|
|
|
.as_ref()
|
|
|
|
.map(|m| Arc::new(m.dependencies.clone()))
|
|
|
|
.unwrap_or_else(|| self.dependencies.clone());
|
|
|
|
let maybe_types_dependency = maybe_module
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|m| Some(Arc::new(m.maybe_types_dependency.clone()?)))
|
|
|
|
.or_else(|| self.maybe_types_dependency.clone());
|
2021-11-12 11:42:04 -05:00
|
|
|
let line_index = if index_valid == IndexValid::All {
|
2021-09-07 10:39:32 -04:00
|
|
|
line_index
|
2021-01-22 05:03:16 -05:00
|
|
|
} else {
|
2024-06-05 11:04:16 -04:00
|
|
|
Arc::new(LineIndex::new(text.as_ref()))
|
2021-01-22 05:03:16 -05:00
|
|
|
};
|
2024-04-19 21:00:03 -04:00
|
|
|
let maybe_test_module_fut =
|
2024-04-22 18:55:20 -04:00
|
|
|
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &self.config);
|
2024-04-06 10:36:43 -04:00
|
|
|
Ok(Arc::new(Self {
|
2024-04-22 18:55:20 -04:00
|
|
|
config: self.config.clone(),
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script: maybe_module.as_ref().map(|m| m.is_script),
|
2024-04-06 10:36:43 -04:00
|
|
|
specifier: self.specifier.clone(),
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: self.file_referrer.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_fs_version: self.maybe_fs_version.clone(),
|
2024-04-06 10:36:43 -04:00
|
|
|
maybe_language_id: self.maybe_language_id,
|
2021-11-16 17:23:25 -05:00
|
|
|
dependencies,
|
2024-04-18 20:51:16 -04:00
|
|
|
maybe_types_dependency,
|
2024-06-05 11:04:16 -04:00
|
|
|
text,
|
|
|
|
text_info_cell: once_cell::sync::OnceCell::new(),
|
2021-11-12 11:42:04 -05:00
|
|
|
line_index,
|
2024-04-06 10:36:43 -04:00
|
|
|
maybe_headers: self.maybe_headers.clone(),
|
2022-12-19 20:22:17 -05:00
|
|
|
maybe_navigation_tree: Mutex::new(None),
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type,
|
2024-11-27 09:50:38 -05:00
|
|
|
resolution_mode,
|
2024-04-19 21:00:03 -04:00
|
|
|
open_data: self.open_data.is_some().then_some(DocumentOpenData {
|
2024-04-22 14:24:00 -04:00
|
|
|
lsp_version: version,
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_parsed_source,
|
perf(lsp): Cache semantic tokens for open documents (#23799)
VScode will typically send a `textDocument/semanticTokens/full` request
followed by `textDocument/semanticTokens/range`, and occassionally
request semantic tokens even when we know nothing has changed. Semantic
tokens also get refreshed on each change. Computing semantic tokens is
relatively heavy in TSC, so we should avoid it as much as possible.
Caches the semantic tokens for open documents, to avoid making TSC do
unnecessary work. Results in a noticeable improvement in local
benchmarking
before:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 383ms)
- Big Document/Several Edits
(5 runs, mean: 1079ms)
- Find/Replace
(10 runs, mean: 59ms)
- Code Lens
(10 runs, mean: 440ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 9921ms)
<- End benchmarking lsp
```
after:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 395ms)
- Big Document/Several Edits
(5 runs, mean: 1024ms)
- Find/Replace
(10 runs, mean: 56ms)
- Code Lens
(10 runs, mean: 438ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 8927ms)
<- End benchmarking lsp
```
2024-05-14 21:51:48 -04:00
|
|
|
maybe_semantic_tokens: Default::default(),
|
2024-04-19 21:00:03 -04:00
|
|
|
}),
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver: self.resolver.clone(),
|
2024-04-06 10:36:43 -04:00
|
|
|
}))
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
pub fn closed(&self, cache: &Arc<LspCache>) -> Arc<Self> {
|
2024-04-22 14:24:00 -04:00
|
|
|
Arc::new(Self {
|
2024-04-22 18:55:20 -04:00
|
|
|
config: self.config.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
specifier: self.specifier.clone(),
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: self.file_referrer.clone(),
|
2024-06-17 16:54:23 -04:00
|
|
|
maybe_fs_version: calculate_fs_version(
|
|
|
|
cache,
|
|
|
|
&self.specifier,
|
|
|
|
self.file_referrer.as_ref(),
|
|
|
|
),
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_language_id: self.maybe_language_id,
|
|
|
|
dependencies: self.dependencies.clone(),
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script: self.is_script,
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_types_dependency: self.maybe_types_dependency.clone(),
|
2024-06-05 11:04:16 -04:00
|
|
|
text: self.text.clone(),
|
|
|
|
text_info_cell: once_cell::sync::OnceCell::new(),
|
2024-04-22 14:24:00 -04:00
|
|
|
line_index: self.line_index.clone(),
|
|
|
|
maybe_headers: self.maybe_headers.clone(),
|
|
|
|
maybe_navigation_tree: Mutex::new(
|
|
|
|
self.maybe_navigation_tree.lock().clone(),
|
|
|
|
),
|
|
|
|
maybe_test_module_fut: self.maybe_test_module_fut.clone(),
|
|
|
|
media_type: self.media_type,
|
|
|
|
open_data: None,
|
2024-11-27 09:50:38 -05:00
|
|
|
resolution_mode: self.resolution_mode,
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver: self.resolver.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
pub fn saved(&self, cache: &Arc<LspCache>) -> Arc<Self> {
|
2024-04-06 10:36:43 -04:00
|
|
|
Arc::new(Self {
|
2024-04-22 18:55:20 -04:00
|
|
|
config: self.config.clone(),
|
2024-04-06 10:36:43 -04:00
|
|
|
specifier: self.specifier.clone(),
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: self.file_referrer.clone(),
|
2024-06-17 16:54:23 -04:00
|
|
|
maybe_fs_version: calculate_fs_version(
|
|
|
|
cache,
|
|
|
|
&self.specifier,
|
|
|
|
self.file_referrer.as_ref(),
|
|
|
|
),
|
2024-04-06 10:36:43 -04:00
|
|
|
maybe_language_id: self.maybe_language_id,
|
|
|
|
dependencies: self.dependencies.clone(),
|
2024-11-13 10:10:09 -05:00
|
|
|
is_script: self.is_script,
|
2024-04-18 20:51:16 -04:00
|
|
|
maybe_types_dependency: self.maybe_types_dependency.clone(),
|
2024-06-05 11:04:16 -04:00
|
|
|
text: self.text.clone(),
|
|
|
|
text_info_cell: once_cell::sync::OnceCell::new(),
|
2024-04-06 10:36:43 -04:00
|
|
|
line_index: self.line_index.clone(),
|
|
|
|
maybe_headers: self.maybe_headers.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
maybe_navigation_tree: Mutex::new(
|
|
|
|
self.maybe_navigation_tree.lock().clone(),
|
|
|
|
),
|
2024-04-19 21:00:03 -04:00
|
|
|
maybe_test_module_fut: self.maybe_test_module_fut.clone(),
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type: self.media_type,
|
2024-04-19 21:00:03 -04:00
|
|
|
open_data: self.open_data.clone(),
|
2024-11-27 09:50:38 -05:00
|
|
|
resolution_mode: self.resolution_mode,
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver: self.resolver.clone(),
|
2024-04-06 10:36:43 -04:00
|
|
|
})
|
2023-10-12 10:37:56 -04:00
|
|
|
}
|
|
|
|
|
2021-11-12 11:42:04 -05:00
|
|
|
pub fn specifier(&self) -> &ModuleSpecifier {
|
2024-04-06 10:36:43 -04:00
|
|
|
&self.specifier
|
2021-06-21 02:43:35 -04:00
|
|
|
}
|
2021-06-24 19:06:51 -04:00
|
|
|
|
2024-05-23 12:31:56 -04:00
|
|
|
pub fn file_referrer(&self) -> Option<&ModuleSpecifier> {
|
|
|
|
self.file_referrer.as_ref()
|
|
|
|
}
|
|
|
|
|
2024-06-26 18:47:01 -04:00
|
|
|
pub fn scope(&self) -> Option<&ModuleSpecifier> {
|
|
|
|
self
|
|
|
|
.file_referrer
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|r| self.config.tree.scope_for_specifier(r))
|
|
|
|
}
|
|
|
|
|
2024-06-05 11:04:16 -04:00
|
|
|
pub fn content(&self) -> &Arc<str> {
|
|
|
|
&self.text
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
|
2024-11-27 09:50:38 -05:00
|
|
|
pub fn resolution_mode(&self) -> ResolutionMode {
|
|
|
|
self.resolution_mode
|
|
|
|
}
|
|
|
|
|
2024-06-05 11:04:16 -04:00
|
|
|
pub fn text_info(&self) -> &SourceTextInfo {
|
|
|
|
// try to get the text info from the parsed source and if
|
|
|
|
// not then create one in the cell
|
|
|
|
self
|
|
|
|
.maybe_parsed_source()
|
|
|
|
.and_then(|p| p.as_ref().ok())
|
|
|
|
.map(|p| p.text_info_lazy())
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
self
|
|
|
|
.text_info_cell
|
|
|
|
.get_or_init(|| SourceTextInfo::new(self.text.clone()))
|
|
|
|
})
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
pub fn line_index(&self) -> Arc<LineIndex> {
|
2024-04-06 10:36:43 -04:00
|
|
|
self.line_index.clone()
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
pub fn maybe_headers(&self) -> Option<&HashMap<String, String>> {
|
|
|
|
self.maybe_headers.as_ref()
|
|
|
|
}
|
|
|
|
|
2024-04-22 14:24:00 -04:00
|
|
|
fn maybe_fs_version(&self) -> Option<&str> {
|
|
|
|
self.maybe_fs_version.as_deref()
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn script_version(&self) -> String {
|
2024-04-22 14:24:00 -04:00
|
|
|
match (self.maybe_fs_version(), self.maybe_lsp_version()) {
|
|
|
|
(None, None) => "1".to_string(),
|
|
|
|
(None, Some(lsp_version)) => format!("1+{lsp_version}"),
|
|
|
|
(Some(fs_version), None) => fs_version.to_string(),
|
|
|
|
(Some(fs_version), Some(lsp_version)) => {
|
|
|
|
format!("{fs_version}+{lsp_version}")
|
|
|
|
}
|
|
|
|
}
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_diagnosable(&self) -> bool {
|
2024-04-19 21:00:03 -04:00
|
|
|
media_type_is_diagnosable(self.media_type())
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
|
2021-11-12 11:42:04 -05:00
|
|
|
pub fn is_open(&self) -> bool {
|
2024-04-22 14:24:00 -04:00
|
|
|
self.open_data.is_some()
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
|
2024-04-18 20:51:16 -04:00
|
|
|
pub fn maybe_types_dependency(&self) -> &Resolution {
|
|
|
|
if let Some(types_dep) = self.maybe_types_dependency.as_deref() {
|
|
|
|
&types_dep.dependency
|
2022-01-31 17:33:57 -05:00
|
|
|
} else {
|
2024-04-18 20:51:16 -04:00
|
|
|
&Resolution::None
|
2022-01-31 17:33:57 -05:00
|
|
|
}
|
2021-11-07 19:50:48 -05:00
|
|
|
}
|
|
|
|
|
2021-11-12 11:42:04 -05:00
|
|
|
pub fn media_type(&self) -> MediaType {
|
2024-04-15 17:50:52 -04:00
|
|
|
self.media_type
|
2022-12-20 15:19:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_language_id(&self) -> Option<LanguageId> {
|
2024-04-06 10:36:43 -04:00
|
|
|
self.maybe_language_id
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
|
2021-11-12 11:42:04 -05:00
|
|
|
/// Returns the current language server client version if any.
|
|
|
|
pub fn maybe_lsp_version(&self) -> Option<i32> {
|
2024-04-22 14:24:00 -04:00
|
|
|
self.open_data.as_ref().map(|d| d.lsp_version)
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_parsed_source(
|
|
|
|
&self,
|
2024-06-05 11:04:16 -04:00
|
|
|
) -> Option<&Result<deno_ast::ParsedSource, deno_ast::ParseDiagnostic>> {
|
|
|
|
self.open_data.as_ref()?.maybe_parsed_source.as_ref()
|
2024-04-19 21:00:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn maybe_test_module(&self) -> Option<Arc<TestModule>> {
|
|
|
|
self.maybe_test_module_fut.clone()?.await
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_navigation_tree(&self) -> Option<Arc<tsc::NavigationTree>> {
|
2024-04-06 10:36:43 -04:00
|
|
|
self.maybe_navigation_tree.lock().clone()
|
2022-12-19 20:22:17 -05:00
|
|
|
}
|
|
|
|
|
2023-02-22 14:15:25 -05:00
|
|
|
pub fn dependencies(&self) -> &IndexMap<String, deno_graph::Dependency> {
|
2024-04-18 20:51:16 -04:00
|
|
|
self.dependencies.as_ref()
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// If the supplied position is within a dependency range, return the resolved
|
|
|
|
/// string specifier for the dependency, the resolved dependency and the range
|
|
|
|
/// in the source document of the specifier.
|
|
|
|
pub fn get_maybe_dependency(
|
|
|
|
&self,
|
|
|
|
position: &lsp::Position,
|
|
|
|
) -> Option<(String, deno_graph::Dependency, deno_graph::Range)> {
|
|
|
|
let position = deno_graph::Position {
|
|
|
|
line: position.line as usize,
|
|
|
|
character: position.character as usize,
|
2021-10-28 19:56:01 -04:00
|
|
|
};
|
2024-04-18 20:51:16 -04:00
|
|
|
self.dependencies().iter().find_map(|(s, dep)| {
|
2021-11-12 11:42:04 -05:00
|
|
|
dep
|
2024-11-26 14:38:24 -05:00
|
|
|
.includes(position)
|
2021-11-12 11:42:04 -05:00
|
|
|
.map(|r| (s.clone(), dep.clone(), r.clone()))
|
|
|
|
})
|
2021-06-24 19:06:51 -04:00
|
|
|
}
|
2024-04-22 14:24:00 -04:00
|
|
|
|
|
|
|
pub fn cache_navigation_tree(
|
|
|
|
&self,
|
|
|
|
navigation_tree: Arc<tsc::NavigationTree>,
|
|
|
|
) {
|
|
|
|
*self.maybe_navigation_tree.lock() = Some(navigation_tree);
|
|
|
|
}
|
perf(lsp): Cache semantic tokens for open documents (#23799)
VScode will typically send a `textDocument/semanticTokens/full` request
followed by `textDocument/semanticTokens/range`, and occassionally
request semantic tokens even when we know nothing has changed. Semantic
tokens also get refreshed on each change. Computing semantic tokens is
relatively heavy in TSC, so we should avoid it as much as possible.
Caches the semantic tokens for open documents, to avoid making TSC do
unnecessary work. Results in a noticeable improvement in local
benchmarking
before:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 383ms)
- Big Document/Several Edits
(5 runs, mean: 1079ms)
- Find/Replace
(10 runs, mean: 59ms)
- Code Lens
(10 runs, mean: 440ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 9921ms)
<- End benchmarking lsp
```
after:
```
Starting Deno benchmark
-> Start benchmarking lsp
- Simple Startup/Shutdown
(10 runs, mean: 395ms)
- Big Document/Several Edits
(5 runs, mean: 1024ms)
- Find/Replace
(10 runs, mean: 56ms)
- Code Lens
(10 runs, mean: 438ms)
- deco-cx/apps Multiple Edits + Navigation
(5 runs, mean: 8927ms)
<- End benchmarking lsp
```
2024-05-14 21:51:48 -04:00
|
|
|
|
|
|
|
pub fn cache_semantic_tokens_full(
|
|
|
|
&self,
|
|
|
|
semantic_tokens: lsp::SemanticTokens,
|
|
|
|
) {
|
|
|
|
if let Some(open_data) = self.open_data.as_ref() {
|
|
|
|
*open_data.maybe_semantic_tokens.lock() = Some(semantic_tokens);
|
|
|
|
}
|
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2024-04-15 17:50:52 -04:00
|
|
|
fn resolve_media_type(
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
maybe_headers: Option<&HashMap<String, String>>,
|
|
|
|
maybe_language_id: Option<LanguageId>,
|
|
|
|
) -> MediaType {
|
2024-04-22 14:24:00 -04:00
|
|
|
if let Some(language_id) = maybe_language_id {
|
|
|
|
return MediaType::from_specifier_and_content_type(
|
|
|
|
specifier,
|
|
|
|
language_id.as_content_type(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-04-15 17:50:52 -04:00
|
|
|
if maybe_headers.is_some() {
|
|
|
|
return MediaType::from_specifier_and_headers(specifier, maybe_headers);
|
|
|
|
}
|
|
|
|
|
2024-04-22 14:24:00 -04:00
|
|
|
MediaType::from_specifier(specifier)
|
2024-04-15 17:50:52 -04:00
|
|
|
}
|
|
|
|
|
2024-11-26 14:38:24 -05:00
|
|
|
pub fn to_lsp_range(referrer: &deno_graph::Range) -> lsp::Range {
|
2021-10-28 19:56:01 -04:00
|
|
|
lsp::Range {
|
|
|
|
start: lsp::Position {
|
2024-11-26 14:38:24 -05:00
|
|
|
line: referrer.range.start.line as u32,
|
|
|
|
character: referrer.range.start.character as u32,
|
2021-10-28 19:56:01 -04:00
|
|
|
},
|
|
|
|
end: lsp::Position {
|
2024-11-26 14:38:24 -05:00
|
|
|
line: referrer.range.end.line as u32,
|
|
|
|
character: referrer.range.end.character as u32,
|
2021-10-28 19:56:01 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
struct FileSystemDocuments {
|
2024-04-09 18:12:55 -04:00
|
|
|
docs: DashMap<ModuleSpecifier, Arc<Document>>,
|
|
|
|
dirty: AtomicBool,
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2021-10-28 19:56:01 -04:00
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
impl FileSystemDocuments {
|
2022-11-30 14:19:32 -05:00
|
|
|
pub fn get(
|
2024-04-09 18:12:55 -04:00
|
|
|
&self,
|
2022-11-29 19:32:18 -05:00
|
|
|
specifier: &ModuleSpecifier,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: &Arc<LspResolver>,
|
2024-04-22 18:55:20 -04:00
|
|
|
config: &Arc<Config>,
|
2024-05-09 15:22:27 -04:00
|
|
|
cache: &Arc<LspCache>,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Option<Arc<Document>> {
|
2024-06-17 16:54:23 -04:00
|
|
|
let file_referrer = Some(specifier)
|
2024-06-28 13:18:21 -04:00
|
|
|
.filter(|s| cache.is_valid_file_referrer(s))
|
2024-06-17 16:54:23 -04:00
|
|
|
.or(file_referrer);
|
|
|
|
let new_fs_version = calculate_fs_version(cache, specifier, file_referrer);
|
2024-04-22 14:24:00 -04:00
|
|
|
let old_doc = self.docs.get(specifier).map(|v| v.value().clone());
|
|
|
|
let dirty = match &old_doc {
|
|
|
|
None => true,
|
|
|
|
Some(old_doc) => {
|
|
|
|
match (old_doc.maybe_fs_version(), new_fs_version.as_deref()) {
|
|
|
|
(None, None) => {
|
|
|
|
matches!(specifier.scheme(), "file" | "http" | "https")
|
|
|
|
}
|
|
|
|
(old, new) => old != new,
|
|
|
|
}
|
|
|
|
}
|
2023-07-01 18:52:30 -04:00
|
|
|
};
|
2024-04-22 14:24:00 -04:00
|
|
|
if dirty {
|
2022-11-29 19:32:18 -05:00
|
|
|
// attempt to update the file on the file system
|
2024-11-27 09:50:38 -05:00
|
|
|
self.refresh_document(specifier, resolver, config, cache, file_referrer)
|
2022-11-29 19:32:18 -05:00
|
|
|
} else {
|
2024-04-22 14:24:00 -04:00
|
|
|
old_doc
|
2022-11-29 19:32:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-25 09:21:59 -05:00
|
|
|
/// Adds or updates a document by reading the document from the file system
|
|
|
|
/// returning the document.
|
2021-11-18 13:50:24 -05:00
|
|
|
fn refresh_document(
|
2024-04-09 18:12:55 -04:00
|
|
|
&self,
|
2022-01-24 03:01:33 -05:00
|
|
|
specifier: &ModuleSpecifier,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: &Arc<LspResolver>,
|
2024-04-22 18:55:20 -04:00
|
|
|
config: &Arc<Config>,
|
2024-05-09 15:22:27 -04:00
|
|
|
cache: &Arc<LspCache>,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Option<Arc<Document>> {
|
2021-10-28 19:56:01 -04:00
|
|
|
let doc = if specifier.scheme() == "file" {
|
2024-09-28 07:55:01 -04:00
|
|
|
let path = url_to_file_path(specifier).ok()?;
|
2023-07-01 18:52:30 -04:00
|
|
|
let bytes = fs::read(path).ok()?;
|
2024-11-19 18:59:23 -05:00
|
|
|
let content = bytes_to_content(
|
|
|
|
specifier,
|
|
|
|
MediaType::from_specifier(specifier),
|
|
|
|
bytes,
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
.ok()?;
|
2021-10-28 19:56:01 -04:00
|
|
|
Document::new(
|
|
|
|
specifier.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
content.into(),
|
|
|
|
None,
|
|
|
|
None,
|
2021-10-28 19:56:01 -04:00
|
|
|
None,
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver.clone(),
|
|
|
|
config.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
cache,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer.cloned(),
|
2021-10-28 19:56:01 -04:00
|
|
|
)
|
2023-07-01 18:52:30 -04:00
|
|
|
} else if specifier.scheme() == "data" {
|
2024-01-31 22:15:22 -05:00
|
|
|
let source = deno_graph::source::RawDataUrl::parse(specifier)
|
|
|
|
.ok()?
|
|
|
|
.decode()
|
|
|
|
.ok()?;
|
2023-07-01 18:52:30 -04:00
|
|
|
Document::new(
|
|
|
|
specifier.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
source.into(),
|
|
|
|
None,
|
|
|
|
None,
|
2023-07-01 18:52:30 -04:00
|
|
|
None,
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver.clone(),
|
|
|
|
config.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
cache,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer.cloned(),
|
2023-07-01 18:52:30 -04:00
|
|
|
)
|
2021-10-28 19:56:01 -04:00
|
|
|
} else {
|
2024-06-17 16:54:23 -04:00
|
|
|
let http_cache = cache.for_specifier(file_referrer);
|
2024-05-09 15:22:27 -04:00
|
|
|
let cache_key = http_cache.cache_item_key(specifier).ok()?;
|
2024-08-26 19:59:17 -04:00
|
|
|
let cached_file = http_cache.get(&cache_key, None).ok()??;
|
2024-01-31 22:15:22 -05:00
|
|
|
let (_, maybe_charset) =
|
|
|
|
deno_graph::source::resolve_media_type_and_charset_from_headers(
|
|
|
|
specifier,
|
2024-08-26 19:59:17 -04:00
|
|
|
Some(&cached_file.metadata.headers),
|
2024-01-31 22:15:22 -05:00
|
|
|
);
|
2024-11-19 18:59:23 -05:00
|
|
|
let media_type = resolve_media_type(
|
|
|
|
specifier,
|
|
|
|
Some(&cached_file.metadata.headers),
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
let content = bytes_to_content(
|
2024-01-31 22:15:22 -05:00
|
|
|
specifier,
|
2024-11-19 18:59:23 -05:00
|
|
|
media_type,
|
2024-11-29 18:54:26 -05:00
|
|
|
cached_file.content.into_owned(),
|
2024-01-31 22:15:22 -05:00
|
|
|
maybe_charset,
|
|
|
|
)
|
|
|
|
.ok()?;
|
2021-10-28 19:56:01 -04:00
|
|
|
Document::new(
|
|
|
|
specifier.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
content.into(),
|
|
|
|
None,
|
|
|
|
None,
|
2024-11-19 18:59:23 -05:00
|
|
|
Some(cached_file.metadata.headers),
|
2024-04-22 18:55:20 -04:00
|
|
|
resolver.clone(),
|
|
|
|
config.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
cache,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer.cloned(),
|
2021-10-28 19:56:01 -04:00
|
|
|
)
|
|
|
|
};
|
2022-01-25 09:21:59 -05:00
|
|
|
self.docs.insert(specifier.clone(), doc.clone());
|
2024-04-09 18:12:55 -04:00
|
|
|
self.set_dirty(true);
|
2022-01-25 09:21:59 -05:00
|
|
|
Some(doc)
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
2024-04-09 18:12:55 -04:00
|
|
|
|
|
|
|
pub fn remove_document(
|
|
|
|
&self,
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
) -> Option<Arc<Document>> {
|
|
|
|
Some(self.docs.remove(specifier)?.1)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the dirty flag to the provided value and returns the previous value.
|
|
|
|
pub fn set_dirty(&self, dirty: bool) -> bool {
|
|
|
|
self.dirty.swap(dirty, Ordering::Relaxed)
|
|
|
|
}
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2021-10-28 19:56:01 -04:00
|
|
|
|
2023-03-29 16:25:48 -04:00
|
|
|
/// Specify the documents to include on a `documents.documents(...)` call.
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub enum DocumentsFilter {
|
|
|
|
/// Includes all the documents (diagnosable & non-diagnosable, open & file system).
|
|
|
|
All,
|
|
|
|
/// Includes all the diagnosable documents (open & file system).
|
|
|
|
AllDiagnosable,
|
|
|
|
/// Includes only the diagnosable documents that are open.
|
|
|
|
OpenDiagnosable,
|
|
|
|
}
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
#[derive(Debug, Default, Clone)]
|
2022-03-23 09:54:22 -04:00
|
|
|
pub struct Documents {
|
2021-11-18 13:50:24 -05:00
|
|
|
/// The DENO_DIR that the documents looks for non-file based modules.
|
2024-05-09 15:22:27 -04:00
|
|
|
cache: Arc<LspCache>,
|
2024-04-19 21:00:03 -04:00
|
|
|
config: Arc<Config>,
|
2024-11-13 10:10:09 -05:00
|
|
|
/// A resolver that takes into account currently loaded import map and JSX
|
|
|
|
/// settings.
|
|
|
|
resolver: Arc<LspResolver>,
|
2021-11-18 13:50:24 -05:00
|
|
|
/// A flag that indicates that stated data is potentially invalid and needs to
|
|
|
|
/// be recalculated before being considered valid.
|
|
|
|
dirty: bool,
|
|
|
|
/// A map of documents that are "open" in the language server.
|
2024-04-06 10:36:43 -04:00
|
|
|
open_docs: HashMap<ModuleSpecifier, Arc<Document>>,
|
2021-11-18 13:50:24 -05:00
|
|
|
/// Documents stored on the file system.
|
2024-04-09 18:12:55 -04:00
|
|
|
file_system_docs: Arc<FileSystemDocuments>,
|
2024-11-15 09:40:32 -05:00
|
|
|
dep_info_by_scope: Arc<BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>>,
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Documents {
|
|
|
|
/// "Open" a document from the perspective of the editor, meaning that
|
|
|
|
/// requests for information from the document will come from the in-memory
|
|
|
|
/// representation received from the language server client, versus reading
|
|
|
|
/// information from the disk.
|
|
|
|
pub fn open(
|
|
|
|
&mut self,
|
|
|
|
specifier: ModuleSpecifier,
|
|
|
|
version: i32,
|
|
|
|
language_id: LanguageId,
|
2022-05-20 16:40:55 -04:00
|
|
|
content: Arc<str>,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<ModuleSpecifier>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Arc<Document> {
|
2024-04-22 14:24:00 -04:00
|
|
|
let document = Document::new(
|
2021-11-18 13:50:24 -05:00
|
|
|
specifier.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
content,
|
|
|
|
Some(version),
|
|
|
|
Some(language_id),
|
2024-04-15 17:50:52 -04:00
|
|
|
// todo(dsherret): don't we want to pass in the headers from
|
|
|
|
// the cache for remote modules here in order to get the
|
|
|
|
// x-typescript-types?
|
|
|
|
None,
|
2024-04-22 18:55:20 -04:00
|
|
|
self.resolver.clone(),
|
|
|
|
self.config.clone(),
|
2024-04-22 14:24:00 -04:00
|
|
|
&self.cache,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer,
|
2021-11-18 13:50:24 -05:00
|
|
|
);
|
2024-04-09 18:12:55 -04:00
|
|
|
|
|
|
|
self.file_system_docs.remove_document(&specifier);
|
|
|
|
self.file_system_docs.set_dirty(true);
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
self.open_docs.insert(specifier, document.clone());
|
|
|
|
self.dirty = true;
|
|
|
|
document
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Apply language server content changes to an open document.
|
|
|
|
pub fn change(
|
2021-01-22 05:03:16 -05:00
|
|
|
&mut self,
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
version: i32,
|
2021-10-28 19:56:01 -04:00
|
|
|
changes: Vec<lsp::TextDocumentContentChangeEvent>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Result<Arc<Document>, AnyError> {
|
2021-11-18 13:50:24 -05:00
|
|
|
let doc = self
|
|
|
|
.open_docs
|
|
|
|
.get(specifier)
|
|
|
|
.cloned()
|
2024-04-09 18:12:55 -04:00
|
|
|
.or_else(|| self.file_system_docs.remove_document(specifier))
|
2023-03-15 17:46:36 -04:00
|
|
|
.map(Ok)
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
Err(custom_error(
|
|
|
|
"NotFound",
|
|
|
|
format!("The specifier \"{specifier}\" was not found."),
|
|
|
|
))
|
|
|
|
})?;
|
2021-10-28 19:56:01 -04:00
|
|
|
self.dirty = true;
|
2024-11-27 09:50:38 -05:00
|
|
|
let doc = doc.with_change(version, changes)?;
|
2021-11-18 13:50:24 -05:00
|
|
|
self.open_docs.insert(doc.specifier().clone(), doc.clone());
|
2021-11-12 11:42:04 -05:00
|
|
|
Ok(doc)
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
|
2023-10-12 10:37:56 -04:00
|
|
|
pub fn save(&mut self, specifier: &ModuleSpecifier) {
|
2024-04-09 18:12:55 -04:00
|
|
|
let doc = self
|
|
|
|
.open_docs
|
|
|
|
.get(specifier)
|
|
|
|
.cloned()
|
|
|
|
.or_else(|| self.file_system_docs.remove_document(specifier));
|
2023-10-12 10:37:56 -04:00
|
|
|
let Some(doc) = doc else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
self.dirty = true;
|
|
|
|
let doc = doc.saved(&self.cache);
|
|
|
|
self.open_docs.insert(doc.specifier().clone(), doc.clone());
|
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
/// Close an open document, this essentially clears any editor state that is
|
|
|
|
/// being held, and the document store will revert to the file system if
|
|
|
|
/// information about the document is required.
|
2024-04-22 14:24:00 -04:00
|
|
|
pub fn close(&mut self, specifier: &ModuleSpecifier) {
|
2023-10-12 10:37:56 -04:00
|
|
|
if let Some(document) = self.open_docs.remove(specifier) {
|
2024-04-22 14:24:00 -04:00
|
|
|
let document = document.closed(&self.cache);
|
2024-04-09 18:12:55 -04:00
|
|
|
self
|
|
|
|
.file_system_docs
|
|
|
|
.docs
|
|
|
|
.insert(specifier.clone(), document);
|
|
|
|
|
2023-10-12 10:37:56 -04:00
|
|
|
self.dirty = true;
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2024-04-17 16:40:42 -04:00
|
|
|
pub fn release(&self, specifier: &ModuleSpecifier) {
|
|
|
|
self.file_system_docs.remove_document(specifier);
|
|
|
|
self.file_system_docs.set_dirty(true);
|
|
|
|
}
|
|
|
|
|
2024-05-23 12:31:56 -04:00
|
|
|
pub fn get_file_referrer<'a>(
|
|
|
|
&self,
|
|
|
|
specifier: &'a ModuleSpecifier,
|
|
|
|
) -> Option<Cow<'a, ModuleSpecifier>> {
|
2024-06-28 13:18:21 -04:00
|
|
|
if self.is_valid_file_referrer(specifier) {
|
|
|
|
return Some(Cow::Borrowed(specifier));
|
2024-05-23 12:31:56 -04:00
|
|
|
}
|
2024-06-28 13:18:21 -04:00
|
|
|
self
|
|
|
|
.get(specifier)
|
|
|
|
.and_then(|d| d.file_referrer().cloned().map(Cow::Owned))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_valid_file_referrer(&self, specifier: &ModuleSpecifier) -> bool {
|
|
|
|
self.cache.is_valid_file_referrer(specifier)
|
2024-05-23 12:31:56 -04:00
|
|
|
}
|
|
|
|
|
2024-05-13 12:55:31 -04:00
|
|
|
pub fn resolve_document_specifier(
|
2023-08-02 16:57:25 -04:00
|
|
|
&self,
|
|
|
|
specifier: &ModuleSpecifier,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2023-08-02 16:57:25 -04:00
|
|
|
) -> Option<ModuleSpecifier> {
|
2024-05-09 10:17:31 -04:00
|
|
|
let specifier = if let Ok(jsr_req_ref) =
|
|
|
|
JsrPackageReqReference::from_specifier(specifier)
|
|
|
|
{
|
2024-05-23 12:31:56 -04:00
|
|
|
Cow::Owned(
|
|
|
|
self
|
|
|
|
.resolver
|
2024-06-10 12:03:17 -04:00
|
|
|
.jsr_to_resource_url(&jsr_req_ref, file_referrer)?,
|
2024-05-23 12:31:56 -04:00
|
|
|
)
|
2023-12-06 19:03:18 -05:00
|
|
|
} else {
|
2024-05-09 10:17:31 -04:00
|
|
|
Cow::Borrowed(specifier)
|
|
|
|
};
|
|
|
|
if !DOCUMENT_SCHEMES.contains(&specifier.scheme()) {
|
|
|
|
return None;
|
2023-12-06 19:03:18 -05:00
|
|
|
}
|
2024-05-23 12:31:56 -04:00
|
|
|
self.resolver.resolve_redirects(&specifier, file_referrer)
|
2023-08-02 16:57:25 -04:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
/// Return `true` if the specifier can be resolved to a document.
|
2024-05-23 12:31:56 -04:00
|
|
|
pub fn exists(
|
|
|
|
&self,
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
|
|
|
) -> bool {
|
|
|
|
let specifier = self.resolve_document_specifier(specifier, file_referrer);
|
2022-01-25 09:21:59 -05:00
|
|
|
if let Some(specifier) = specifier {
|
|
|
|
if self.open_docs.contains_key(&specifier) {
|
|
|
|
return true;
|
|
|
|
}
|
2023-07-01 18:52:30 -04:00
|
|
|
if specifier.scheme() == "data" {
|
|
|
|
return true;
|
|
|
|
}
|
2023-07-08 16:06:45 -04:00
|
|
|
if specifier.scheme() == "file" {
|
2024-09-28 07:55:01 -04:00
|
|
|
return url_to_file_path(&specifier)
|
2023-07-08 16:06:45 -04:00
|
|
|
.map(|p| p.is_file())
|
|
|
|
.unwrap_or(false);
|
|
|
|
}
|
2024-06-17 16:54:23 -04:00
|
|
|
if self.cache.for_specifier(file_referrer).contains(&specifier) {
|
2023-07-08 16:06:45 -04:00
|
|
|
return true;
|
2022-01-25 09:21:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2024-11-15 09:40:32 -05:00
|
|
|
pub fn dep_info_by_scope(
|
2024-06-11 13:14:36 -04:00
|
|
|
&mut self,
|
2024-11-15 09:40:32 -05:00
|
|
|
) -> Arc<BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>> {
|
|
|
|
self.calculate_dep_info_if_dirty();
|
|
|
|
self.dep_info_by_scope.clone()
|
2022-10-21 11:20:18 -04:00
|
|
|
}
|
|
|
|
|
2024-11-15 09:40:32 -05:00
|
|
|
pub fn scopes_with_node_specifier(&self) -> HashSet<Option<ModuleSpecifier>> {
|
|
|
|
self
|
|
|
|
.dep_info_by_scope
|
|
|
|
.iter()
|
|
|
|
.filter(|(_, i)| i.has_node_specifier)
|
|
|
|
.map(|(s, _)| s.clone())
|
|
|
|
.collect::<HashSet<_>>()
|
2023-01-24 09:05:54 -05:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
/// Return a document for the specifier.
|
2024-05-23 12:31:56 -04:00
|
|
|
pub fn get(&self, specifier: &ModuleSpecifier) -> Option<Arc<Document>> {
|
|
|
|
if let Some(document) = self.open_docs.get(specifier) {
|
|
|
|
Some(document.clone())
|
|
|
|
} else {
|
|
|
|
let old_doc = self
|
|
|
|
.file_system_docs
|
|
|
|
.docs
|
|
|
|
.get(specifier)
|
|
|
|
.map(|d| d.value().clone());
|
|
|
|
if let Some(old_doc) = old_doc {
|
|
|
|
self.file_system_docs.get(
|
|
|
|
specifier,
|
|
|
|
&self.resolver,
|
|
|
|
&self.config,
|
|
|
|
&self.cache,
|
|
|
|
old_doc.file_referrer(),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a document for the specifier.
|
|
|
|
pub fn get_or_load(
|
2024-04-06 10:36:43 -04:00
|
|
|
&self,
|
2024-05-23 12:31:56 -04:00
|
|
|
specifier: &ModuleSpecifier,
|
2024-08-12 10:45:27 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2024-04-06 10:36:43 -04:00
|
|
|
) -> Option<Arc<Document>> {
|
2024-05-23 12:31:56 -04:00
|
|
|
let specifier =
|
2024-08-12 10:45:27 -04:00
|
|
|
self.resolve_document_specifier(specifier, file_referrer)?;
|
2021-11-18 13:50:24 -05:00
|
|
|
if let Some(document) = self.open_docs.get(&specifier) {
|
|
|
|
Some(document.clone())
|
2021-10-28 19:56:01 -04:00
|
|
|
} else {
|
2024-04-09 18:12:55 -04:00
|
|
|
self.file_system_docs.get(
|
2023-10-20 00:02:08 -04:00
|
|
|
&specifier,
|
2024-04-22 18:55:20 -04:00
|
|
|
&self.resolver,
|
|
|
|
&self.config,
|
|
|
|
&self.cache,
|
2024-08-12 10:45:27 -04:00
|
|
|
file_referrer,
|
2023-10-20 00:02:08 -04:00
|
|
|
)
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2023-03-29 16:25:48 -04:00
|
|
|
/// Return a collection of documents that are contained in the document store
|
|
|
|
/// based on the provided filter.
|
2024-04-06 10:36:43 -04:00
|
|
|
pub fn documents(&self, filter: DocumentsFilter) -> Vec<Arc<Document>> {
|
2023-03-29 16:25:48 -04:00
|
|
|
match filter {
|
|
|
|
DocumentsFilter::OpenDiagnosable => self
|
2021-11-18 13:50:24 -05:00
|
|
|
.open_docs
|
|
|
|
.values()
|
|
|
|
.filter_map(|doc| {
|
2023-03-29 16:25:48 -04:00
|
|
|
if doc.is_diagnosable() {
|
2021-11-18 13:50:24 -05:00
|
|
|
Some(doc.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
2023-03-29 16:25:48 -04:00
|
|
|
.collect(),
|
|
|
|
DocumentsFilter::AllDiagnosable | DocumentsFilter::All => {
|
|
|
|
let diagnosable_only =
|
|
|
|
matches!(filter, DocumentsFilter::AllDiagnosable);
|
|
|
|
// it is technically possible for a Document to end up in both the open
|
|
|
|
// and closed documents so we need to ensure we don't return duplicates
|
|
|
|
let mut seen_documents = HashSet::new();
|
|
|
|
self
|
|
|
|
.open_docs
|
|
|
|
.values()
|
2024-04-09 18:12:55 -04:00
|
|
|
.cloned()
|
|
|
|
.chain(self.file_system_docs.docs.iter().map(|v| v.value().clone()))
|
2023-03-29 16:25:48 -04:00
|
|
|
.filter_map(|doc| {
|
|
|
|
// this prefers the open documents
|
|
|
|
if seen_documents.insert(doc.specifier().clone())
|
|
|
|
&& (!diagnosable_only || doc.is_diagnosable())
|
|
|
|
{
|
2024-04-09 18:12:55 -04:00
|
|
|
Some(doc)
|
2023-03-29 16:25:48 -04:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
/// For a given set of string specifiers, resolve each one from the graph,
|
|
|
|
/// for a given referrer. This is used to provide resolution information to
|
|
|
|
/// tsc when type checking.
|
|
|
|
pub fn resolve(
|
|
|
|
&self,
|
2024-11-26 14:38:24 -05:00
|
|
|
// (is_cjs: bool, raw_specifier: String)
|
|
|
|
raw_specifiers: &[(bool, String)],
|
2024-04-14 17:42:58 -04:00
|
|
|
referrer: &ModuleSpecifier,
|
2024-07-03 15:25:42 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2023-01-24 09:05:54 -05:00
|
|
|
) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
|
2024-11-13 10:10:09 -05:00
|
|
|
let referrer_doc = self.get(referrer);
|
|
|
|
let file_referrer = referrer_doc
|
2024-07-03 15:25:42 -04:00
|
|
|
.as_ref()
|
|
|
|
.and_then(|d| d.file_referrer())
|
|
|
|
.or(file_referrer);
|
2024-11-13 10:10:09 -05:00
|
|
|
let dependencies = referrer_doc.as_ref().map(|d| d.dependencies());
|
2021-10-28 19:56:01 -04:00
|
|
|
let mut results = Vec::new();
|
2024-11-26 14:38:24 -05:00
|
|
|
for (is_cjs, raw_specifier) in raw_specifiers {
|
|
|
|
let resolution_mode = match is_cjs {
|
|
|
|
true => ResolutionMode::Require,
|
|
|
|
false => ResolutionMode::Import,
|
|
|
|
};
|
2024-09-04 10:00:44 -04:00
|
|
|
if raw_specifier.starts_with("asset:") {
|
|
|
|
if let Ok(specifier) = ModuleSpecifier::parse(raw_specifier) {
|
2023-03-21 11:46:40 -04:00
|
|
|
let media_type = MediaType::from_specifier(&specifier);
|
2021-11-16 17:23:25 -05:00
|
|
|
results.push(Some((specifier, media_type)));
|
|
|
|
} else {
|
|
|
|
results.push(None);
|
|
|
|
}
|
2023-01-24 09:05:54 -05:00
|
|
|
} else if let Some(dep) =
|
2024-09-04 10:00:44 -04:00
|
|
|
dependencies.as_ref().and_then(|d| d.get(raw_specifier))
|
2023-01-24 09:05:54 -05:00
|
|
|
{
|
2023-02-09 22:00:23 -05:00
|
|
|
if let Some(specifier) = dep.maybe_type.maybe_specifier() {
|
2024-05-23 12:31:56 -04:00
|
|
|
results.push(self.resolve_dependency(
|
|
|
|
specifier,
|
|
|
|
referrer,
|
2024-11-26 14:38:24 -05:00
|
|
|
resolution_mode,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer,
|
|
|
|
));
|
2023-02-09 22:00:23 -05:00
|
|
|
} else if let Some(specifier) = dep.maybe_code.maybe_specifier() {
|
2024-05-23 12:31:56 -04:00
|
|
|
results.push(self.resolve_dependency(
|
|
|
|
specifier,
|
|
|
|
referrer,
|
2024-11-26 14:38:24 -05:00
|
|
|
resolution_mode,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer,
|
|
|
|
));
|
2021-01-22 05:03:16 -05:00
|
|
|
} else {
|
2021-10-28 19:56:01 -04:00
|
|
|
results.push(None);
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
2024-05-23 12:31:56 -04:00
|
|
|
} else if let Ok(specifier) =
|
2024-11-13 10:10:09 -05:00
|
|
|
self.resolver.as_cli_resolver(file_referrer).resolve(
|
2024-09-04 10:00:44 -04:00
|
|
|
raw_specifier,
|
2024-11-26 14:38:24 -05:00
|
|
|
referrer,
|
|
|
|
deno_graph::Position::zeroed(),
|
|
|
|
resolution_mode,
|
|
|
|
NodeResolutionKind::Types,
|
2024-05-23 12:31:56 -04:00
|
|
|
)
|
|
|
|
{
|
|
|
|
results.push(self.resolve_dependency(
|
|
|
|
&specifier,
|
|
|
|
referrer,
|
2024-11-26 14:38:24 -05:00
|
|
|
resolution_mode,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer,
|
|
|
|
));
|
2021-11-16 17:23:25 -05:00
|
|
|
} else {
|
|
|
|
results.push(None);
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
}
|
2023-01-24 09:05:54 -05:00
|
|
|
results
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
|
|
|
|
2024-03-26 11:52:20 -04:00
|
|
|
pub fn update_config(
|
|
|
|
&mut self,
|
|
|
|
config: &Config,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: &Arc<LspResolver>,
|
2024-05-09 15:22:27 -04:00
|
|
|
cache: &LspCache,
|
2024-06-11 16:06:43 -04:00
|
|
|
workspace_files: &IndexSet<ModuleSpecifier>,
|
2024-03-26 11:52:20 -04:00
|
|
|
) {
|
2024-04-19 21:00:03 -04:00
|
|
|
self.config = Arc::new(config.clone());
|
2024-05-09 15:22:27 -04:00
|
|
|
self.cache = Arc::new(cache.clone());
|
2024-04-26 16:39:33 -04:00
|
|
|
self.resolver = resolver.clone();
|
2024-11-13 10:10:09 -05:00
|
|
|
|
|
|
|
node_resolver::PackageJsonThreadLocalCache::clear();
|
|
|
|
|
2024-03-21 00:29:52 -04:00
|
|
|
{
|
2024-04-09 18:12:55 -04:00
|
|
|
let fs_docs = &self.file_system_docs;
|
2024-03-21 00:29:52 -04:00
|
|
|
// Clean up non-existent documents.
|
|
|
|
fs_docs.docs.retain(|specifier, _| {
|
2024-09-28 07:55:01 -04:00
|
|
|
let Ok(path) = url_to_file_path(specifier) else {
|
2024-03-21 00:29:52 -04:00
|
|
|
// Remove non-file schemed docs (deps). They may not be dependencies
|
|
|
|
// anymore after updating resolvers.
|
|
|
|
return false;
|
|
|
|
};
|
2024-03-26 11:52:20 -04:00
|
|
|
if !config.specifier_enabled(specifier) {
|
2024-03-21 00:29:52 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
path.is_file()
|
|
|
|
});
|
|
|
|
let mut open_docs = std::mem::take(&mut self.open_docs);
|
2024-04-09 18:12:55 -04:00
|
|
|
for doc in open_docs.values_mut() {
|
|
|
|
if !config.specifier_enabled(doc.specifier()) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-11-27 09:50:38 -05:00
|
|
|
*doc = doc.with_new_config(self.resolver.clone(), self.config.clone());
|
2024-04-09 18:12:55 -04:00
|
|
|
}
|
|
|
|
for mut doc in self.file_system_docs.docs.iter_mut() {
|
|
|
|
if !config.specifier_enabled(doc.specifier()) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-11-27 09:50:38 -05:00
|
|
|
*doc.value_mut() =
|
|
|
|
doc.with_new_config(self.resolver.clone(), self.config.clone());
|
2023-01-28 10:18:32 -05:00
|
|
|
}
|
2024-03-21 00:29:52 -04:00
|
|
|
self.open_docs = open_docs;
|
2024-03-31 16:39:23 -04:00
|
|
|
let mut preload_count = 0;
|
2024-03-26 11:52:20 -04:00
|
|
|
for specifier in workspace_files {
|
|
|
|
if !config.specifier_enabled(specifier) {
|
2024-03-21 00:29:52 -04:00
|
|
|
continue;
|
|
|
|
}
|
2024-03-31 16:39:23 -04:00
|
|
|
if preload_count >= config.settings.unscoped.document_preload_limit {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
preload_count += 1;
|
2024-03-21 00:29:52 -04:00
|
|
|
if !self.open_docs.contains_key(specifier)
|
|
|
|
&& !fs_docs.docs.contains_key(specifier)
|
2023-05-11 17:17:14 -04:00
|
|
|
{
|
2023-10-20 00:02:08 -04:00
|
|
|
fs_docs.refresh_document(
|
2024-03-21 00:29:52 -04:00
|
|
|
specifier,
|
2024-04-22 18:55:20 -04:00
|
|
|
&self.resolver,
|
|
|
|
&self.config,
|
|
|
|
&self.cache,
|
2024-05-23 12:31:56 -04:00
|
|
|
None,
|
2023-10-20 00:02:08 -04:00
|
|
|
);
|
2023-03-30 17:47:53 -04:00
|
|
|
}
|
|
|
|
}
|
2024-04-09 18:12:55 -04:00
|
|
|
fs_docs.set_dirty(true);
|
2023-03-30 17:47:53 -04:00
|
|
|
}
|
2024-03-21 00:29:52 -04:00
|
|
|
self.dirty = true;
|
2023-01-28 10:18:32 -05:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:50:24 -05:00
|
|
|
/// Iterate through the documents, building a map where the key is a unique
|
|
|
|
/// document and the value is a set of specifiers that depend on that
|
|
|
|
/// document.
|
2024-11-15 09:40:32 -05:00
|
|
|
fn calculate_dep_info_if_dirty(&mut self) {
|
|
|
|
let mut dep_info_by_scope: BTreeMap<_, ScopeDepInfo> = Default::default();
|
2024-04-10 14:58:49 -04:00
|
|
|
let is_fs_docs_dirty = self.file_system_docs.set_dirty(false);
|
|
|
|
if !is_fs_docs_dirty && !self.dirty {
|
|
|
|
return;
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2024-04-10 14:58:49 -04:00
|
|
|
let mut visit_doc = |doc: &Arc<Document>| {
|
2024-06-26 18:47:01 -04:00
|
|
|
let scope = doc.scope();
|
2024-11-15 09:40:32 -05:00
|
|
|
let dep_info = dep_info_by_scope.entry(scope.cloned()).or_default();
|
2024-04-10 14:58:49 -04:00
|
|
|
for dependency in doc.dependencies().values() {
|
2024-11-15 09:40:32 -05:00
|
|
|
let code_specifier = dependency.get_code();
|
|
|
|
let type_specifier = dependency.get_type();
|
|
|
|
if let Some(dep) = code_specifier {
|
2024-04-10 14:58:49 -04:00
|
|
|
if dep.scheme() == "node" {
|
2024-11-15 09:40:32 -05:00
|
|
|
dep_info.has_node_specifier = true;
|
2024-04-10 14:58:49 -04:00
|
|
|
}
|
2023-02-21 12:03:48 -05:00
|
|
|
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
|
2024-11-15 09:40:32 -05:00
|
|
|
dep_info.npm_reqs.insert(reference.into_inner().req);
|
2022-11-29 19:32:18 -05:00
|
|
|
}
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2024-11-15 09:40:32 -05:00
|
|
|
if let Some(dep) = type_specifier {
|
2024-04-10 14:58:49 -04:00
|
|
|
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
|
2024-11-15 09:40:32 -05:00
|
|
|
dep_info.npm_reqs.insert(reference.into_inner().req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dependency.maybe_deno_types_specifier.is_some() {
|
|
|
|
if let (Some(code_specifier), Some(type_specifier)) =
|
|
|
|
(code_specifier, type_specifier)
|
|
|
|
{
|
|
|
|
if MediaType::from_specifier(type_specifier).is_declaration() {
|
|
|
|
dep_info
|
|
|
|
.deno_types_to_code_resolutions
|
|
|
|
.insert(type_specifier.clone(), code_specifier.clone());
|
|
|
|
}
|
2022-11-29 19:32:18 -05:00
|
|
|
}
|
|
|
|
}
|
2024-04-10 14:58:49 -04:00
|
|
|
}
|
|
|
|
if let Some(dep) = doc.maybe_types_dependency().maybe_specifier() {
|
|
|
|
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
|
2024-11-15 09:40:32 -05:00
|
|
|
dep_info.npm_reqs.insert(reference.into_inner().req);
|
2022-11-29 19:32:18 -05:00
|
|
|
}
|
|
|
|
}
|
2024-04-10 14:58:49 -04:00
|
|
|
};
|
2024-04-09 18:12:55 -04:00
|
|
|
for entry in self.file_system_docs.docs.iter() {
|
2024-04-10 14:58:49 -04:00
|
|
|
visit_doc(entry.value())
|
2024-04-09 18:12:55 -04:00
|
|
|
}
|
2024-04-10 14:58:49 -04:00
|
|
|
for doc in self.open_docs.values() {
|
|
|
|
visit_doc(doc);
|
2022-10-21 11:20:18 -04:00
|
|
|
}
|
2022-11-29 19:32:18 -05:00
|
|
|
|
2024-06-17 16:54:23 -04:00
|
|
|
for (scope, config_data) in self.config.tree.data_by_scope().as_ref() {
|
2024-11-15 09:40:32 -05:00
|
|
|
let dep_info = dep_info_by_scope.entry(Some(scope.clone())).or_default();
|
|
|
|
(|| {
|
|
|
|
let config_file = config_data.maybe_deno_json()?;
|
|
|
|
let jsx_config =
|
|
|
|
config_file.to_maybe_jsx_import_source_config().ok()??;
|
|
|
|
let type_specifier = jsx_config.default_types_specifier.as_ref()?;
|
|
|
|
let code_specifier = jsx_config.default_specifier.as_ref()?;
|
|
|
|
let cli_resolver = self.resolver.as_cli_resolver(Some(scope));
|
|
|
|
let type_specifier = cli_resolver
|
|
|
|
.resolve(
|
|
|
|
type_specifier,
|
2024-11-26 14:38:24 -05:00
|
|
|
&jsx_config.base_url,
|
|
|
|
deno_graph::Position::zeroed(),
|
2024-11-15 09:40:32 -05:00
|
|
|
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
|
2024-11-26 14:38:24 -05:00
|
|
|
ResolutionMode::Import,
|
|
|
|
NodeResolutionKind::Types,
|
2024-11-15 09:40:32 -05:00
|
|
|
)
|
|
|
|
.ok()?;
|
|
|
|
let code_specifier = cli_resolver
|
|
|
|
.resolve(
|
|
|
|
code_specifier,
|
2024-11-26 14:38:24 -05:00
|
|
|
&jsx_config.base_url,
|
|
|
|
deno_graph::Position::zeroed(),
|
2024-11-15 09:40:32 -05:00
|
|
|
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
|
2024-11-26 14:38:24 -05:00
|
|
|
ResolutionMode::Import,
|
|
|
|
NodeResolutionKind::Execution,
|
2024-11-15 09:40:32 -05:00
|
|
|
)
|
|
|
|
.ok()?;
|
|
|
|
dep_info
|
|
|
|
.deno_types_to_code_resolutions
|
|
|
|
.insert(type_specifier, code_specifier);
|
|
|
|
Some(())
|
|
|
|
})();
|
|
|
|
// fill the reqs from the lockfile
|
2024-06-17 16:54:23 -04:00
|
|
|
if let Some(lockfile) = config_data.lockfile.as_ref() {
|
|
|
|
let lockfile = lockfile.lock();
|
2024-08-28 14:17:47 -04:00
|
|
|
for dep_req in lockfile.content.packages.specifiers.keys() {
|
|
|
|
if dep_req.kind == deno_semver::package::PackageKind::Npm {
|
2024-11-15 09:40:32 -05:00
|
|
|
dep_info.npm_reqs.insert(dep_req.req.clone());
|
2024-04-05 18:33:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 09:05:54 -05:00
|
|
|
// 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.
|
2024-11-15 09:40:32 -05:00
|
|
|
for dep_info in dep_info_by_scope.values_mut() {
|
|
|
|
if dep_info.has_node_specifier
|
|
|
|
&& !dep_info.npm_reqs.iter().any(|r| r.name == "@types/node")
|
|
|
|
{
|
|
|
|
dep_info
|
|
|
|
.npm_reqs
|
|
|
|
.insert(PackageReq::from_str("@types/node").unwrap());
|
2024-06-11 13:14:36 -04:00
|
|
|
}
|
2023-01-24 09:05:54 -05:00
|
|
|
}
|
|
|
|
|
2024-11-15 09:40:32 -05:00
|
|
|
self.dep_info_by_scope = Arc::new(
|
|
|
|
dep_info_by_scope
|
|
|
|
.into_iter()
|
|
|
|
.map(|(s, i)| (s, Arc::new(i)))
|
|
|
|
.collect(),
|
|
|
|
);
|
2021-11-18 13:50:24 -05:00
|
|
|
self.dirty = false;
|
2021-11-12 11:42:04 -05:00
|
|
|
}
|
|
|
|
|
2024-05-16 17:12:25 -04:00
|
|
|
pub fn resolve_dependency(
|
2021-10-28 19:56:01 -04:00
|
|
|
&self,
|
|
|
|
specifier: &ModuleSpecifier,
|
2023-10-03 19:05:06 -04:00
|
|
|
referrer: &ModuleSpecifier,
|
2024-11-26 14:38:24 -05:00
|
|
|
resolution_mode: ResolutionMode,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2021-11-18 13:50:24 -05:00
|
|
|
) -> Option<(ModuleSpecifier, MediaType)> {
|
2023-07-26 18:52:31 -04:00
|
|
|
if let Some(module_name) = specifier.as_str().strip_prefix("node:") {
|
|
|
|
if deno_node::is_builtin_node_module(module_name) {
|
|
|
|
// return itself for node: specifiers because during type checking
|
|
|
|
// we resolve to the ambient modules in the @types/node package
|
|
|
|
// rather than deno_std/node
|
|
|
|
return Some((specifier.clone(), MediaType::Dts));
|
|
|
|
}
|
|
|
|
}
|
2024-06-28 13:18:21 -04:00
|
|
|
let mut specifier = specifier.clone();
|
|
|
|
let mut media_type = None;
|
|
|
|
if let Ok(npm_ref) = NpmPackageReqReference::from_specifier(&specifier) {
|
2024-11-13 10:10:09 -05:00
|
|
|
let (s, mt) = self.resolver.npm_to_file_url(
|
|
|
|
&npm_ref,
|
|
|
|
referrer,
|
2024-11-26 14:38:24 -05:00
|
|
|
resolution_mode,
|
2024-11-13 10:10:09 -05:00
|
|
|
file_referrer,
|
|
|
|
)?;
|
2024-06-28 13:18:21 -04:00
|
|
|
specifier = s;
|
|
|
|
media_type = Some(mt);
|
2022-10-21 11:20:18 -04:00
|
|
|
}
|
2024-08-12 10:45:27 -04:00
|
|
|
let Some(doc) = self.get_or_load(&specifier, file_referrer) else {
|
2024-06-28 13:18:21 -04:00
|
|
|
let media_type =
|
|
|
|
media_type.unwrap_or_else(|| MediaType::from_specifier(&specifier));
|
|
|
|
return Some((specifier, media_type));
|
2024-04-14 17:42:58 -04:00
|
|
|
};
|
2024-05-23 12:31:56 -04:00
|
|
|
if let Some(types) = doc.maybe_types_dependency().maybe_specifier() {
|
2024-11-27 09:50:38 -05:00
|
|
|
self.resolve_dependency(
|
|
|
|
types,
|
|
|
|
&specifier,
|
|
|
|
doc.resolution_mode(),
|
|
|
|
file_referrer,
|
|
|
|
)
|
2021-11-18 13:50:24 -05:00
|
|
|
} else {
|
2024-06-28 13:18:21 -04:00
|
|
|
Some((doc.specifier().clone(), doc.media_type()))
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2021-10-28 19:56:01 -04:00
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2022-10-20 13:23:21 -04:00
|
|
|
/// Loader that will look at the open documents.
|
2022-10-21 11:20:18 -04:00
|
|
|
pub struct OpenDocumentsGraphLoader<'a> {
|
2022-10-20 13:23:21 -04:00
|
|
|
pub inner_loader: &'a mut dyn deno_graph::source::Loader,
|
2024-04-06 10:36:43 -04:00
|
|
|
pub open_docs: &'a HashMap<ModuleSpecifier, Arc<Document>>,
|
2022-10-20 13:23:21 -04:00
|
|
|
}
|
|
|
|
|
2023-09-07 09:09:16 -04:00
|
|
|
impl<'a> OpenDocumentsGraphLoader<'a> {
|
|
|
|
fn load_from_docs(
|
|
|
|
&self,
|
2022-10-20 13:23:21 -04:00
|
|
|
specifier: &ModuleSpecifier,
|
2023-09-07 09:09:16 -04:00
|
|
|
) -> Option<deno_graph::source::LoadFuture> {
|
2022-10-20 13:23:21 -04:00
|
|
|
if specifier.scheme() == "file" {
|
|
|
|
if let Some(doc) = self.open_docs.get(specifier) {
|
2023-09-07 09:09:16 -04:00
|
|
|
return Some(
|
|
|
|
future::ready(Ok(Some(deno_graph::source::LoadResponse::Module {
|
2024-06-05 11:04:16 -04:00
|
|
|
content: Arc::from(doc.content().clone()),
|
2022-10-20 13:23:21 -04:00
|
|
|
specifier: doc.specifier().clone(),
|
|
|
|
maybe_headers: None,
|
2023-09-07 09:09:16 -04:00
|
|
|
})))
|
|
|
|
.boxed_local(),
|
|
|
|
);
|
2022-10-20 13:23:21 -04:00
|
|
|
}
|
|
|
|
}
|
2023-09-07 09:09:16 -04:00
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> deno_graph::source::Loader for OpenDocumentsGraphLoader<'a> {
|
|
|
|
fn load(
|
2024-04-18 20:51:16 -04:00
|
|
|
&self,
|
2023-09-07 09:09:16 -04:00
|
|
|
specifier: &ModuleSpecifier,
|
2024-02-15 14:49:35 -05:00
|
|
|
options: deno_graph::source::LoadOptions,
|
2023-09-07 09:09:16 -04:00
|
|
|
) -> deno_graph::source::LoadFuture {
|
2024-05-09 10:17:31 -04:00
|
|
|
match self.load_from_docs(specifier) {
|
2023-09-07 09:09:16 -04:00
|
|
|
Some(fut) => fut,
|
2024-05-09 10:17:31 -04:00
|
|
|
None => self.inner_loader.load(specifier, options),
|
2023-09-07 09:09:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn cache_module_info(
|
2024-04-18 20:51:16 -04:00
|
|
|
&self,
|
2023-09-07 09:09:16 -04:00
|
|
|
specifier: &deno_ast::ModuleSpecifier,
|
2024-09-05 10:22:13 -04:00
|
|
|
media_type: MediaType,
|
2024-01-31 22:15:22 -05:00
|
|
|
source: &Arc<[u8]>,
|
2023-09-07 09:09:16 -04:00
|
|
|
module_info: &deno_graph::ModuleInfo,
|
|
|
|
) {
|
2024-09-05 10:22:13 -04:00
|
|
|
self.inner_loader.cache_module_info(
|
|
|
|
specifier,
|
|
|
|
media_type,
|
|
|
|
source,
|
|
|
|
module_info,
|
|
|
|
)
|
2022-10-20 13:23:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-28 10:18:32 -05:00
|
|
|
fn parse_and_analyze_module(
|
2024-05-28 14:58:43 -04:00
|
|
|
specifier: ModuleSpecifier,
|
2024-06-05 11:04:16 -04:00
|
|
|
text: Arc<str>,
|
2023-01-28 10:18:32 -05:00
|
|
|
maybe_headers: Option<&HashMap<String, String>>,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type: MediaType,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: &LspResolver,
|
2024-11-27 09:50:38 -05:00
|
|
|
) -> (
|
|
|
|
Option<ParsedSourceResult>,
|
|
|
|
Option<ModuleResult>,
|
|
|
|
ResolutionMode,
|
|
|
|
) {
|
2024-06-05 11:04:16 -04:00
|
|
|
let parsed_source_result = parse_source(specifier.clone(), text, media_type);
|
2024-11-27 09:50:38 -05:00
|
|
|
let (module_result, resolution_mode) = analyze_module(
|
2024-05-23 12:31:56 -04:00
|
|
|
specifier,
|
|
|
|
&parsed_source_result,
|
|
|
|
maybe_headers,
|
|
|
|
file_referrer,
|
|
|
|
resolver,
|
|
|
|
);
|
2024-11-27 09:50:38 -05:00
|
|
|
(
|
|
|
|
Some(parsed_source_result),
|
|
|
|
Some(module_result),
|
|
|
|
resolution_mode,
|
|
|
|
)
|
2023-01-28 10:18:32 -05:00
|
|
|
}
|
2022-08-22 12:14:59 -04:00
|
|
|
|
2023-01-28 10:18:32 -05:00
|
|
|
fn parse_source(
|
2024-05-28 14:58:43 -04:00
|
|
|
specifier: ModuleSpecifier,
|
2024-06-05 11:04:16 -04:00
|
|
|
text: Arc<str>,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type: MediaType,
|
2023-01-28 10:18:32 -05:00
|
|
|
) -> ParsedSourceResult {
|
2024-11-01 12:27:00 -04:00
|
|
|
deno_ast::parse_program(deno_ast::ParseParams {
|
2024-05-28 14:58:43 -04:00
|
|
|
specifier,
|
2024-06-05 11:04:16 -04:00
|
|
|
text,
|
2024-04-15 17:50:52 -04:00
|
|
|
media_type,
|
2023-01-28 10:18:32 -05:00
|
|
|
capture_tokens: true,
|
|
|
|
scope_analysis: true,
|
|
|
|
maybe_syntax: None,
|
|
|
|
})
|
2022-08-22 12:14:59 -04:00
|
|
|
}
|
|
|
|
|
2023-01-28 10:18:32 -05:00
|
|
|
fn analyze_module(
|
2024-05-28 14:58:43 -04:00
|
|
|
specifier: ModuleSpecifier,
|
2023-01-28 10:18:32 -05:00
|
|
|
parsed_source_result: &ParsedSourceResult,
|
2022-08-22 12:14:59 -04:00
|
|
|
maybe_headers: Option<&HashMap<String, String>>,
|
2024-05-23 12:31:56 -04:00
|
|
|
file_referrer: Option<&ModuleSpecifier>,
|
2024-04-26 16:39:33 -04:00
|
|
|
resolver: &LspResolver,
|
2024-11-27 09:50:38 -05:00
|
|
|
) -> (ModuleResult, ResolutionMode) {
|
2023-01-28 10:18:32 -05:00
|
|
|
match parsed_source_result {
|
2024-06-05 11:04:16 -04:00
|
|
|
Ok(parsed_source) => {
|
|
|
|
let npm_resolver = resolver.create_graph_npm_resolver(file_referrer);
|
2024-11-13 10:10:09 -05:00
|
|
|
let cli_resolver = resolver.as_cli_resolver(file_referrer);
|
2024-11-27 09:50:38 -05:00
|
|
|
let is_cjs_resolver = resolver.as_is_cjs_resolver(file_referrer);
|
2024-11-13 10:10:09 -05:00
|
|
|
let config_data = resolver.as_config_data(file_referrer);
|
|
|
|
let valid_referrer = specifier.clone();
|
|
|
|
let jsx_import_source_config =
|
|
|
|
config_data.and_then(|d| d.maybe_jsx_import_source_config());
|
2024-11-27 09:50:38 -05:00
|
|
|
let module_resolution_mode = is_cjs_resolver.get_lsp_resolution_mode(
|
|
|
|
&specifier,
|
|
|
|
Some(parsed_source.compute_is_script()),
|
|
|
|
);
|
2024-11-13 10:10:09 -05:00
|
|
|
let resolver = SingleReferrerGraphResolver {
|
|
|
|
valid_referrer: &valid_referrer,
|
2024-11-27 09:50:38 -05:00
|
|
|
module_resolution_mode,
|
2024-11-13 10:10:09 -05:00
|
|
|
cli_resolver,
|
|
|
|
jsx_import_source_config: jsx_import_source_config.as_ref(),
|
|
|
|
};
|
2024-11-27 09:50:38 -05:00
|
|
|
(
|
|
|
|
Ok(deno_graph::parse_module_from_ast(
|
|
|
|
deno_graph::ParseModuleFromAstOptions {
|
|
|
|
graph_kind: deno_graph::GraphKind::TypesOnly,
|
|
|
|
specifier,
|
|
|
|
maybe_headers,
|
|
|
|
parsed_source,
|
|
|
|
// use a null file system because there's no need to bother resolving
|
|
|
|
// dynamic imports like import(`./dir/${something}`) in the LSP
|
|
|
|
file_system: &deno_graph::source::NullFileSystem,
|
|
|
|
jsr_url_provider: &CliJsrUrlProvider,
|
|
|
|
maybe_resolver: Some(&resolver),
|
|
|
|
maybe_npm_resolver: Some(&npm_resolver),
|
|
|
|
},
|
|
|
|
)),
|
|
|
|
module_resolution_mode,
|
|
|
|
)
|
2024-06-05 11:04:16 -04:00
|
|
|
}
|
2024-11-27 09:50:38 -05:00
|
|
|
Err(err) => (
|
|
|
|
Err(deno_graph::ModuleGraphError::ModuleError(
|
|
|
|
deno_graph::ModuleError::ParseErr(specifier, err.clone()),
|
|
|
|
)),
|
|
|
|
ResolutionMode::Import,
|
|
|
|
),
|
2023-01-28 10:18:32 -05:00
|
|
|
}
|
2022-08-22 12:14:59 -04:00
|
|
|
}
|
|
|
|
|
2024-11-19 18:59:23 -05:00
|
|
|
fn bytes_to_content(
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
media_type: MediaType,
|
|
|
|
bytes: Vec<u8>,
|
|
|
|
maybe_charset: Option<&str>,
|
|
|
|
) -> Result<String, AnyError> {
|
|
|
|
if media_type == MediaType::Wasm {
|
|
|
|
// we use the dts representation for Wasm modules
|
|
|
|
Ok(deno_graph::source::wasm::wasm_module_to_dts(&bytes)?)
|
|
|
|
} else {
|
|
|
|
Ok(deno_graph::source::decode_owned_source(
|
|
|
|
specifier,
|
|
|
|
bytes,
|
|
|
|
maybe_charset,
|
|
|
|
)?)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-22 05:03:16 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2024-05-09 15:22:27 -04:00
|
|
|
use crate::lsp::cache::LspCache;
|
2024-07-19 15:56:07 -04:00
|
|
|
|
|
|
|
use deno_config::deno_json::ConfigFile;
|
|
|
|
use deno_config::deno_json::ConfigParseOptions;
|
2024-03-21 00:29:52 -04:00
|
|
|
use deno_core::serde_json;
|
2024-03-26 11:52:20 -04:00
|
|
|
use deno_core::serde_json::json;
|
2023-03-30 17:47:53 -04:00
|
|
|
use pretty_assertions::assert_eq;
|
2022-04-01 11:15:37 -04:00
|
|
|
use test_util::TempDir;
|
2021-01-22 05:03:16 -05:00
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
async fn setup() -> (Documents, LspCache, TempDir) {
|
|
|
|
let temp_dir = TempDir::new();
|
2024-06-28 13:18:21 -04:00
|
|
|
temp_dir.create_dir_all(".deno_dir");
|
2024-08-23 20:21:21 -04:00
|
|
|
let cache = LspCache::new(Some(temp_dir.url().join(".deno_dir").unwrap()));
|
2024-05-09 15:22:27 -04:00
|
|
|
let config = Config::default();
|
2024-05-28 20:26:43 -04:00
|
|
|
let resolver =
|
|
|
|
Arc::new(LspResolver::from_config(&config, &cache, None).await);
|
2024-05-09 15:22:27 -04:00
|
|
|
let mut documents = Documents::default();
|
|
|
|
documents.update_config(&config, &resolver, &cache, &Default::default());
|
|
|
|
(documents, cache, temp_dir)
|
2021-08-18 23:19:12 -04:00
|
|
|
}
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_documents_open_close() {
|
|
|
|
let (mut documents, _, _) = setup().await;
|
2021-10-28 19:56:01 -04:00
|
|
|
let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
|
2022-05-20 16:40:55 -04:00
|
|
|
let content = r#"import * as b from "./b.ts";
|
2021-10-28 19:56:01 -04:00
|
|
|
console.log(b);
|
2022-05-20 16:40:55 -04:00
|
|
|
"#;
|
|
|
|
let document = documents.open(
|
2024-04-22 14:24:00 -04:00
|
|
|
specifier.clone(),
|
2022-05-20 16:40:55 -04:00
|
|
|
1,
|
|
|
|
"javascript".parse().unwrap(),
|
|
|
|
content.into(),
|
2024-05-23 12:31:56 -04:00
|
|
|
None,
|
2021-06-02 06:29:58 -04:00
|
|
|
);
|
2021-11-12 11:42:04 -05:00
|
|
|
assert!(document.is_diagnosable());
|
2024-04-22 14:24:00 -04:00
|
|
|
assert!(document.is_open());
|
|
|
|
assert!(document.maybe_parsed_source().is_some());
|
|
|
|
assert!(document.maybe_lsp_version().is_some());
|
|
|
|
documents.close(&specifier);
|
|
|
|
// We can't use `Documents::get()` here, it will look through the real FS.
|
|
|
|
let document = documents.file_system_docs.docs.get(&specifier).unwrap();
|
|
|
|
assert!(!document.is_open());
|
|
|
|
assert!(document.maybe_parsed_source().is_none());
|
|
|
|
assert!(document.maybe_lsp_version().is_none());
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_documents_change() {
|
|
|
|
let (mut documents, _, _) = setup().await;
|
2021-10-28 19:56:01 -04:00
|
|
|
let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
|
2022-05-20 16:40:55 -04:00
|
|
|
let content = r#"import * as b from "./b.ts";
|
2021-10-28 19:56:01 -04:00
|
|
|
console.log(b);
|
2022-05-20 16:40:55 -04:00
|
|
|
"#;
|
2021-10-28 19:56:01 -04:00
|
|
|
documents.open(
|
2021-06-02 06:29:58 -04:00
|
|
|
specifier.clone(),
|
|
|
|
1,
|
2021-10-28 19:56:01 -04:00
|
|
|
"javascript".parse().unwrap(),
|
2022-05-20 16:40:55 -04:00
|
|
|
content.into(),
|
2024-05-23 12:31:56 -04:00
|
|
|
None,
|
2021-06-02 06:29:58 -04:00
|
|
|
);
|
2021-10-28 19:56:01 -04:00
|
|
|
documents
|
2021-01-22 05:03:16 -05:00
|
|
|
.change(
|
|
|
|
&specifier,
|
|
|
|
2,
|
2021-01-29 14:34:33 -05:00
|
|
|
vec![lsp::TextDocumentContentChangeEvent {
|
|
|
|
range: Some(lsp::Range {
|
|
|
|
start: lsp::Position {
|
2021-10-28 19:56:01 -04:00
|
|
|
line: 1,
|
|
|
|
character: 13,
|
2021-01-22 05:03:16 -05:00
|
|
|
},
|
2021-01-29 14:34:33 -05:00
|
|
|
end: lsp::Position {
|
2021-10-28 19:56:01 -04:00
|
|
|
line: 1,
|
|
|
|
character: 13,
|
2021-01-22 05:03:16 -05:00
|
|
|
},
|
|
|
|
}),
|
2021-10-28 19:56:01 -04:00
|
|
|
range_length: None,
|
|
|
|
text: r#", "hello deno""#.to_string(),
|
2021-01-22 05:03:16 -05:00
|
|
|
}],
|
|
|
|
)
|
2021-10-28 19:56:01 -04:00
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
2024-06-05 11:04:16 -04:00
|
|
|
documents.get(&specifier).unwrap().content().as_ref(),
|
2021-10-28 19:56:01 -04:00
|
|
|
r#"import * as b from "./b.ts";
|
|
|
|
console.log(b, "hello deno");
|
|
|
|
"#
|
2021-08-18 23:19:12 -04:00
|
|
|
);
|
2021-06-02 06:29:58 -04:00
|
|
|
}
|
2021-11-18 13:50:24 -05:00
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_documents_ensure_no_duplicates() {
|
2021-11-18 13:50:24 -05:00
|
|
|
// it should never happen that a user of this API causes this to happen,
|
|
|
|
// but we'll guard against it anyway
|
2024-05-09 15:22:27 -04:00
|
|
|
let (mut documents, _, temp_dir) = setup().await;
|
|
|
|
let file_path = temp_dir.path().join("file.ts");
|
2024-08-23 20:21:21 -04:00
|
|
|
let file_specifier = temp_dir.url().join("file.ts").unwrap();
|
2023-06-10 11:09:45 -04:00
|
|
|
file_path.write("");
|
2021-11-18 13:50:24 -05:00
|
|
|
|
|
|
|
// open the document
|
|
|
|
documents.open(
|
|
|
|
file_specifier.clone(),
|
|
|
|
1,
|
|
|
|
LanguageId::TypeScript,
|
2022-05-20 16:40:55 -04:00
|
|
|
"".into(),
|
2024-05-23 12:31:56 -04:00
|
|
|
None,
|
2021-11-18 13:50:24 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
// make a clone of the document store and close the document in that one
|
|
|
|
let mut documents2 = documents.clone();
|
2024-04-22 14:24:00 -04:00
|
|
|
documents2.close(&file_specifier);
|
2021-11-18 13:50:24 -05:00
|
|
|
|
|
|
|
// At this point the document will be in both documents and the shared file system documents.
|
|
|
|
// Now make sure that the original documents doesn't return both copies
|
2023-03-29 16:25:48 -04:00
|
|
|
assert_eq!(documents.documents(DocumentsFilter::All).len(), 1);
|
2021-11-18 13:50:24 -05:00
|
|
|
}
|
2023-01-28 10:18:32 -05:00
|
|
|
|
2024-03-26 11:52:20 -04:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_documents_refresh_dependencies_config_change() {
|
2023-01-28 10:18:32 -05:00
|
|
|
// it should never happen that a user of this API causes this to happen,
|
|
|
|
// but we'll guard against it anyway
|
2024-05-09 15:22:27 -04:00
|
|
|
let (mut documents, cache, temp_dir) = setup().await;
|
2023-01-28 10:18:32 -05:00
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
let file1_path = temp_dir.path().join("file1.ts");
|
2024-08-23 20:21:21 -04:00
|
|
|
let file1_specifier = temp_dir.url().join("file1.ts").unwrap();
|
2023-01-28 10:18:32 -05:00
|
|
|
fs::write(&file1_path, "").unwrap();
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
let file2_path = temp_dir.path().join("file2.ts");
|
2024-08-23 20:21:21 -04:00
|
|
|
let file2_specifier = temp_dir.url().join("file2.ts").unwrap();
|
2023-01-28 10:18:32 -05:00
|
|
|
fs::write(&file2_path, "").unwrap();
|
|
|
|
|
2024-05-09 15:22:27 -04:00
|
|
|
let file3_path = temp_dir.path().join("file3.ts");
|
2024-08-23 20:21:21 -04:00
|
|
|
let file3_specifier = temp_dir.url().join("file3.ts").unwrap();
|
2023-01-28 10:18:32 -05:00
|
|
|
fs::write(&file3_path, "").unwrap();
|
|
|
|
|
2024-08-23 20:21:21 -04:00
|
|
|
let mut config = Config::new_with_roots([temp_dir.url()]);
|
2024-03-21 00:29:52 -04:00
|
|
|
let workspace_settings =
|
|
|
|
serde_json::from_str(r#"{ "enable": true }"#).unwrap();
|
|
|
|
config.set_workspace_settings(workspace_settings, vec![]);
|
|
|
|
let workspace_files =
|
|
|
|
[&file1_specifier, &file2_specifier, &file3_specifier]
|
|
|
|
.into_iter()
|
|
|
|
.cloned()
|
2024-06-11 16:06:43 -04:00
|
|
|
.collect::<IndexSet<_>>();
|
2024-03-21 00:29:52 -04:00
|
|
|
|
2023-01-28 10:18:32 -05:00
|
|
|
// set the initial import map and point to file 2
|
|
|
|
{
|
2024-03-26 11:52:20 -04:00
|
|
|
config
|
|
|
|
.tree
|
|
|
|
.inject_config_file(
|
|
|
|
ConfigFile::new(
|
|
|
|
&json!({
|
|
|
|
"imports": {
|
|
|
|
"test": "./file2.ts",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
config.root_uri().unwrap().join("deno.json").unwrap(),
|
2024-07-19 15:56:07 -04:00
|
|
|
&ConfigParseOptions::default(),
|
2024-03-26 11:52:20 -04:00
|
|
|
)
|
2023-01-28 10:18:32 -05:00
|
|
|
.unwrap(),
|
2024-03-26 11:52:20 -04:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
2024-05-28 20:26:43 -04:00
|
|
|
let resolver =
|
|
|
|
Arc::new(LspResolver::from_config(&config, &cache, None).await);
|
2024-05-09 15:22:27 -04:00
|
|
|
documents.update_config(&config, &resolver, &cache, &workspace_files);
|
2023-01-28 10:18:32 -05:00
|
|
|
|
|
|
|
// open the document
|
|
|
|
let document = documents.open(
|
|
|
|
file1_specifier.clone(),
|
|
|
|
1,
|
|
|
|
LanguageId::TypeScript,
|
|
|
|
"import {} from 'test';".into(),
|
2024-05-23 12:31:56 -04:00
|
|
|
None,
|
2023-01-28 10:18:32 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
document
|
|
|
|
.dependencies()
|
|
|
|
.get("test")
|
|
|
|
.unwrap()
|
|
|
|
.maybe_code
|
|
|
|
.maybe_specifier()
|
|
|
|
.map(ToOwned::to_owned),
|
|
|
|
Some(file2_specifier),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// now point at file 3
|
|
|
|
{
|
2024-03-26 11:52:20 -04:00
|
|
|
config
|
|
|
|
.tree
|
|
|
|
.inject_config_file(
|
|
|
|
ConfigFile::new(
|
|
|
|
&json!({
|
|
|
|
"imports": {
|
|
|
|
"test": "./file3.ts",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
config.root_uri().unwrap().join("deno.json").unwrap(),
|
2024-07-19 15:56:07 -04:00
|
|
|
&ConfigParseOptions::default(),
|
2024-03-26 11:52:20 -04:00
|
|
|
)
|
2023-01-28 10:18:32 -05:00
|
|
|
.unwrap(),
|
2024-03-26 11:52:20 -04:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
2024-05-28 20:26:43 -04:00
|
|
|
let resolver =
|
|
|
|
Arc::new(LspResolver::from_config(&config, &cache, None).await);
|
2024-05-09 15:22:27 -04:00
|
|
|
documents.update_config(&config, &resolver, &cache, &workspace_files);
|
2023-01-28 10:18:32 -05:00
|
|
|
|
|
|
|
// check the document's dependencies
|
|
|
|
let document = documents.get(&file1_specifier).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
document
|
|
|
|
.dependencies()
|
|
|
|
.get("test")
|
|
|
|
.unwrap()
|
|
|
|
.maybe_code
|
|
|
|
.maybe_specifier()
|
|
|
|
.map(ToOwned::to_owned),
|
|
|
|
Some(file3_specifier),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-01-22 05:03:16 -05:00
|
|
|
}
|