summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-10-29 10:56:01 +1100
committerGitHub <noreply@github.com>2021-10-29 10:56:01 +1100
commit34a9ddff091950aee9d89915cd13944259e7d346 (patch)
tree9ccf8c07c34fe5661c90097584eb8b47da3e9ba9
parent74a93fdf63a17de990954399b10eb6dfe7dd1973 (diff)
refactor(lsp): use deno_graph and single document struct (#12535)
Closes #12473
-rw-r--r--Cargo.lock13
-rw-r--r--cli/Cargo.toml4
-rw-r--r--cli/http_cache.rs1
-rw-r--r--cli/lsp/analysis.rs727
-rw-r--r--cli/lsp/code_lens.rs102
-rw-r--r--cli/lsp/completions.rs60
-rw-r--r--cli/lsp/diagnostics.rs254
-rw-r--r--cli/lsp/document_source.rs75
-rw-r--r--cli/lsp/documents.rs1287
-rw-r--r--cli/lsp/language_server.rs632
-rw-r--r--cli/lsp/mod.rs3
-rw-r--r--cli/lsp/registries.rs19
-rw-r--r--cli/lsp/resolver.rs33
-rw-r--r--cli/lsp/sources.rs797
-rw-r--r--cli/lsp/tsc.rs294
-rw-r--r--cli/lsp/urls.rs4
-rw-r--r--cli/tests/integration/lsp_tests.rs32
17 files changed, 1650 insertions, 2687 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8eaece96f..7324466c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -781,9 +781,9 @@ dependencies = [
[[package]]
name = "deno_doc"
-version = "0.17.1"
+version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96b60f0447a17ed4fc413bd7abad29e7d31e76f13422813f2e7978b0c4bdca7a"
+checksum = "81744eb79bda23580020b2df68a150197312b54c47e11160a65add0534c03ec5"
dependencies = [
"cfg-if 1.0.0",
"deno_ast",
@@ -826,9 +826,9 @@ dependencies = [
[[package]]
name = "deno_graph"
-version = "0.8.2"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3d7197f490abd2a1e7d1edc3a1c4db1f4e294d7067dbbf65c4714932c7c3c70"
+checksum = "d7513c22ec28dd1a4eeb82a99fc543c92d7ee2f1a146a0dd6bea7427020eb863"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
@@ -836,7 +836,7 @@ dependencies = [
"deno_ast",
"futures",
"lazy_static",
- "parking_lot_core",
+ "parking_lot",
"regex",
"ring",
"serde",
@@ -1887,6 +1887,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
dependencies = [
"cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
]
[[package]]
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 3c316f9b0..86d35a0e1 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -41,8 +41,8 @@ winres = "0.1.11"
[dependencies]
deno_ast = { version = "0.4.1", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { version = "0.105.0", path = "../core" }
-deno_doc = "0.17.1"
-deno_graph = "0.8.2"
+deno_doc = "0.18.0"
+deno_graph = "0.9.1"
deno_lint = { version = "0.18.1", features = ["docs"] }
deno_runtime = { version = "0.31.0", path = "../runtime" }
deno_tls = { version = "0.10.0", path = "../ext/tls" }
diff --git a/cli/http_cache.rs b/cli/http_cache.rs
index 9a6a89b56..9f76364de 100644
--- a/cli/http_cache.rs
+++ b/cli/http_cache.rs
@@ -91,7 +91,6 @@ impl Metadata {
Ok(())
}
- #[cfg(test)]
pub fn read(cache_filename: &Path) -> Result<Metadata, AnyError> {
let metadata_filename = Metadata::filename(cache_filename);
let metadata = fs::read_to_string(metadata_filename)?;
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 643de5c56..709131689 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -3,23 +3,10 @@
use super::language_server;
use super::tsc;
-use crate::ast;
-use crate::ast::Location;
use crate::config_file::LintConfig;
-use crate::lsp::documents::DocumentData;
use crate::tools::lint::create_linter;
use crate::tools::lint::get_configured_rules;
-use deno_ast::swc::ast as swc_ast;
-use deno_ast::swc::common::comments::Comment;
-use deno_ast::swc::common::BytePos;
-use deno_ast::swc::common::Span;
-use deno_ast::swc::common::DUMMY_SP;
-use deno_ast::swc::visit::Node;
-use deno_ast::swc::visit::Visit;
-use deno_ast::swc::visit::VisitWith;
-use deno_ast::Diagnostic;
-use deno_ast::MediaType;
use deno_ast::SourceTextInfo;
use deno_core::error::anyhow;
use deno_core::error::custom_error;
@@ -27,17 +14,13 @@ use deno_core::error::AnyError;
use deno_core::serde::Deserialize;
use deno_core::serde_json;
use deno_core::serde_json::json;
-use deno_core::url;
-use deno_core::ModuleResolutionError;
use deno_core::ModuleSpecifier;
-use import_map::ImportMap;
use lspower::lsp;
use lspower::lsp::Position;
use lspower::lsp::Range;
use regex::Regex;
use std::cmp::Ordering;
use std::collections::HashMap;
-use std::fmt;
lazy_static::lazy_static! {
/// Diagnostic error codes which actually are the same, and so when grouping
@@ -84,56 +67,6 @@ lazy_static::lazy_static! {
const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"];
-// TODO(@kitsonk) remove after deno_graph migration
-#[derive(Debug, Clone, Eq, PartialEq)]
-enum TypeScriptReference {
- Path(String),
- Types(String),
-}
-
-fn match_to_span(comment: &Comment, m: &regex::Match) -> Span {
- Span {
- lo: comment.span.lo + BytePos((m.start() + 1) as u32),
- hi: comment.span.lo + BytePos((m.end() + 1) as u32),
- ctxt: comment.span.ctxt,
- }
-}
-
-// TODO(@kitsonk) remove after deno_graph migration
-fn parse_deno_types(comment: &Comment) -> Option<(String, Span)> {
- let captures = DENO_TYPES_RE.captures(&comment.text)?;
- if let Some(m) = captures.get(1) {
- Some((m.as_str().to_string(), match_to_span(comment, &m)))
- } else if let Some(m) = captures.get(2) {
- Some((m.as_str().to_string(), match_to_span(comment, &m)))
- } else {
- unreachable!();
- }
-}
-
-// TODO(@kitsonk) remove after deno_graph migration
-fn parse_ts_reference(
- comment: &Comment,
-) -> Option<(TypeScriptReference, Span)> {
- if !TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) {
- None
- } else if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) {
- let m = captures.get(1).unwrap();
- Some((
- TypeScriptReference::Path(m.as_str().to_string()),
- match_to_span(comment, &m),
- ))
- } else {
- TYPES_REFERENCE_RE.captures(&comment.text).map(|captures| {
- let m = captures.get(1).unwrap();
- (
- TypeScriptReference::Types(m.as_str().to_string()),
- match_to_span(comment, &m),
- )
- })
- }
-}
-
/// Category of self-generated diagnostic messages (those not coming from)
/// TypeScript.
#[derive(Debug, PartialEq, Eq)]
@@ -219,266 +152,6 @@ pub fn get_lint_references(
)
}
-#[derive(Debug, Default, Clone, PartialEq, Eq)]
-pub struct Dependency {
- pub is_dynamic: bool,
- pub maybe_code: Option<ResolvedDependency>,
- pub maybe_code_specifier_range: Option<Range>,
- pub maybe_type: Option<ResolvedDependency>,
- pub maybe_type_specifier_range: Option<Range>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ResolvedDependencyErr {
- InvalidDowngrade,
- InvalidLocalImport,
- InvalidSpecifier(ModuleResolutionError),
- Missing,
-}
-
-impl ResolvedDependencyErr {
- pub fn as_code(&self) -> lsp::NumberOrString {
- match self {
- Self::InvalidDowngrade => {
- lsp::NumberOrString::String("invalid-downgrade".to_string())
- }
- Self::InvalidLocalImport => {
- lsp::NumberOrString::String("invalid-local-import".to_string())
- }
- Self::InvalidSpecifier(error) => match error {
- ModuleResolutionError::ImportPrefixMissing(_, _) => {
- lsp::NumberOrString::String("import-prefix-missing".to_string())
- }
- ModuleResolutionError::InvalidBaseUrl(_) => {
- lsp::NumberOrString::String("invalid-base-url".to_string())
- }
- ModuleResolutionError::InvalidPath(_) => {
- lsp::NumberOrString::String("invalid-path".to_string())
- }
- ModuleResolutionError::InvalidUrl(_) => {
- lsp::NumberOrString::String("invalid-url".to_string())
- }
- },
- Self::Missing => lsp::NumberOrString::String("missing".to_string()),
- }
- }
-}
-
-impl fmt::Display for ResolvedDependencyErr {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::InvalidDowngrade => {
- write!(f, "HTTPS modules cannot import HTTP modules.")
- }
- Self::InvalidLocalImport => {
- write!(f, "Remote modules cannot import local modules.")
- }
- Self::InvalidSpecifier(err) => write!(f, "{}", err),
- Self::Missing => write!(f, "The module is unexpectedly missing."),
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ResolvedDependency {
- Resolved(ModuleSpecifier),
- Err(ResolvedDependencyErr),
-}
-
-impl ResolvedDependency {
- pub fn as_hover_text(&self) -> String {
- match self {
- Self::Resolved(specifier) => match specifier.scheme() {
- "data" => "_(a data url)_".to_string(),
- "blob" => "_(a blob url)_".to_string(),
- _ => format!(
- "{}&#8203;{}",
- specifier[..url::Position::AfterScheme].to_string(),
- specifier[url::Position::AfterScheme..].to_string()
- ),
- },
- Self::Err(_) => "_[errored]_".to_string(),
- }
- }
-}
-
-pub fn resolve_import(
- specifier: &str,
- referrer: &ModuleSpecifier,
- maybe_import_map: &Option<ImportMap>,
-) -> ResolvedDependency {
- let maybe_mapped = if let Some(import_map) = maybe_import_map {
- import_map.resolve(specifier, referrer.as_str()).ok()
- } else {
- None
- };
- let remapped = maybe_mapped.is_some();
- let specifier = if let Some(remapped) = maybe_mapped {
- remapped
- } else {
- match deno_core::resolve_import(specifier, referrer.as_str()) {
- Ok(resolved) => resolved,
- Err(err) => {
- return ResolvedDependency::Err(
- ResolvedDependencyErr::InvalidSpecifier(err),
- )
- }
- }
- };
- let referrer_scheme = referrer.scheme();
- let specifier_scheme = specifier.scheme();
- if referrer_scheme == "https" && specifier_scheme == "http" {
- return ResolvedDependency::Err(ResolvedDependencyErr::InvalidDowngrade);
- }
- if (referrer_scheme == "https" || referrer_scheme == "http")
- && !(specifier_scheme == "https" || specifier_scheme == "http")
- && !remapped
- {
- return ResolvedDependency::Err(ResolvedDependencyErr::InvalidLocalImport);
- }
-
- ResolvedDependency::Resolved(specifier)
-}
-
-pub fn parse_module(
- specifier: &ModuleSpecifier,
- source: SourceTextInfo,
- media_type: MediaType,
-) -> Result<deno_ast::ParsedSource, Diagnostic> {
- deno_ast::parse_module(deno_ast::ParseParams {
- specifier: specifier.as_str().to_string(),
- source,
- media_type,
- // capture the tokens for linting and formatting
- capture_tokens: true,
- maybe_syntax: None,
- scope_analysis: true, // for deno_lint
- })
-}
-
-// TODO(@kitsonk) a lot of this logic is duplicated in module_graph.rs in
-// Module::parse() and should be refactored out to a common function.
-pub fn analyze_dependencies(
- specifier: &ModuleSpecifier,
- media_type: MediaType,
- parsed_source: &deno_ast::ParsedSource,
- maybe_import_map: &Option<ImportMap>,
-) -> (HashMap<String, Dependency>, Option<ResolvedDependency>) {
- let mut maybe_type = None;
- let mut dependencies = HashMap::<String, Dependency>::new();
-
- // Parse leading comments for supported triple slash references.
- for comment in parsed_source.get_leading_comments().iter() {
- if let Some((ts_reference, span)) = parse_ts_reference(comment) {
- let loc = parsed_source.source().line_and_column_index(span.lo);
- match ts_reference {
- TypeScriptReference::Path(import) => {
- let dep = dependencies.entry(import.clone()).or_default();
- let resolved_import =
- resolve_import(&import, specifier, maybe_import_map);
- dep.maybe_code = Some(resolved_import);
- dep.maybe_code_specifier_range = Some(Range {
- start: Position {
- line: loc.line_index as u32,
- character: loc.column_index as u32,
- },
- end: Position {
- line: loc.line_index as u32,
- character: (loc.column_index + import.chars().count() + 2) as u32,
- },
- });
- }
- TypeScriptReference::Types(import) => {
- let resolved_import =
- resolve_import(&import, specifier, maybe_import_map);
- if media_type == MediaType::JavaScript || media_type == MediaType::Jsx
- {
- maybe_type = Some(resolved_import.clone());
- }
- let dep = dependencies.entry(import.clone()).or_default();
- dep.maybe_type = Some(resolved_import);
- dep.maybe_type_specifier_range = Some(Range {
- start: Position {
- line: loc.line_index as u32,
- character: loc.column_index as u32,
- },
- end: Position {
- line: loc.line_index as u32,
- character: (loc.column_index + import.chars().count() + 2) as u32,
- },
- });
- }
- }
- }
- }
-
- // Parse ES and type only imports
- let descriptors = deno_graph::analyze_dependencies(parsed_source);
- for desc in descriptors.into_iter().filter(|desc| {
- desc.kind != deno_ast::swc::dep_graph::DependencyKind::Require
- }) {
- let resolved_import =
- resolve_import(&desc.specifier, specifier, maybe_import_map);
-
- let maybe_resolved_type_dependency =
- // Check for `@deno-types` pragmas that affect the import
- if let Some(comment) = desc.leading_comments.last() {
- parse_deno_types(comment).as_ref().map(|(deno_types, span)| {
- (
- resolve_import(deno_types, specifier, maybe_import_map),
- deno_types.clone(),
- parsed_source.source().line_and_column_index(span.lo)
- )
- })
- } else {
- None
- };
-
- let dep = dependencies.entry(desc.specifier.to_string()).or_default();
- dep.is_dynamic = desc.is_dynamic;
- let start = parsed_source
- .source()
- .line_and_column_index(desc.specifier_span.lo);
- let end = parsed_source
- .source()
- .line_and_column_index(desc.specifier_span.hi);
- let range = Range {
- start: Position {
- line: start.line_index as u32,
- character: start.column_index as u32,
- },
- end: Position {
- line: end.line_index as u32,
- character: end.column_index as u32,
- },
- };
- dep.maybe_code_specifier_range = Some(range);
- dep.maybe_code = Some(resolved_import);
- if dep.maybe_type.is_none() {
- if let Some((resolved_dependency, specifier, loc)) =
- maybe_resolved_type_dependency
- {
- dep.maybe_type_specifier_range = Some(Range {
- start: Position {
- line: loc.line_index as u32,
- // +1 to skip quote
- character: (loc.column_index + 1) as u32,
- },
- end: Position {
- line: loc.line_index as u32,
- // +1 to skip quote
- character: (loc.column_index + 1 + specifier.chars().count())
- as u32,
- },
- });
- dep.maybe_type = Some(resolved_dependency);
- }
- }
- }
-
- (dependencies, maybe_type)
-}
-
fn code_as_string(code: &Option<lsp::NumberOrString>) -> String {
match code {
Some(lsp::NumberOrString::String(str)) => str.clone(),
@@ -494,21 +167,16 @@ fn check_specifier(
specifier: &str,
referrer: &ModuleSpecifier,
snapshot: &language_server::StateSnapshot,
- maybe_import_map: &Option<ImportMap>,
) -> Option<String> {
for ext in SUPPORTED_EXTENSIONS {
let specifier_with_ext = format!("{}{}", specifier, ext);
- if let ResolvedDependency::Resolved(resolved_specifier) =
- resolve_import(&specifier_with_ext, referrer, maybe_import_map)
+ if snapshot
+ .documents
+ .contains_import(&specifier_with_ext, referrer)
{
- if snapshot.documents.contains_key(&resolved_specifier)
- || snapshot.sources.contains_key(&resolved_specifier)
- {
- return Some(specifier_with_ext);
- }
+ return Some(specifier_with_ext);
}
}
-
None
}
@@ -531,12 +199,9 @@ pub(crate) fn fix_ts_import_changes(
.get(1)
.ok_or_else(|| anyhow!("Missing capture."))?
.as_str();
- if let Some(new_specifier) = check_specifier(
- specifier,
- referrer,
- &snapshot,
- &language_server.maybe_import_map,
- ) {
+ if let Some(new_specifier) =
+ check_specifier(specifier, referrer, &snapshot)
+ {
let new_text =
text_change.new_text.replace(specifier, &new_specifier);
text_changes.push(tsc::TextChange {
@@ -582,12 +247,9 @@ fn fix_ts_import_action(
.ok_or_else(|| anyhow!("Missing capture."))?
.as_str();
let snapshot = language_server.snapshot()?;
- if let Some(new_specifier) = check_specifier(
- specifier,
- referrer,
- &snapshot,
- &language_server.maybe_import_map,
- ) {
+ if let Some(new_specifier) =
+ check_specifier(specifier, referrer, &snapshot)
+ {
let description = action.description.replace(specifier, &new_specifier);
let changes = action
.changes
@@ -755,8 +417,9 @@ impl CodeActionCollection {
pub(crate) fn add_deno_lint_ignore_action(
&mut self,
specifier: &ModuleSpecifier,
- document: Option<&DocumentData>,
diagnostic: &lsp::Diagnostic,
+ maybe_text_info: Option<SourceTextInfo>,
+ maybe_parsed_source: Option<deno_ast::ParsedSource>,
) -> Result<(), AnyError> {
let code = diagnostic
.code
@@ -767,11 +430,8 @@ impl CodeActionCollection {
})
.unwrap();
- let document_source = document.map(|d| d.source());
-
- let line_content = document_source.map(|d| {
- d.text_info()
- .line_text(diagnostic.range.start.line as usize)
+ let line_content = maybe_text_info.map(|ti| {
+ ti.line_text(diagnostic.range.start.line as usize)
.to_string()
});
@@ -814,9 +474,7 @@ impl CodeActionCollection {
.push(CodeActionKind::DenoLint(ignore_error_action));
// Disable a lint error for the entire file.
- let parsed_source =
- document_source.and_then(|d| d.module().and_then(|r| r.as_ref().ok()));
- let maybe_ignore_comment = parsed_source.and_then(|ps| {
+ let maybe_ignore_comment = maybe_parsed_source.clone().and_then(|ps| {
// Note: we can use ps.get_leading_comments() but it doesn't
// work when shebang is present at the top of the file.
ps.comments().get_vec().iter().find_map(|c| {
@@ -847,7 +505,7 @@ impl CodeActionCollection {
if let Some(ignore_comment) = maybe_ignore_comment {
new_text = format!(" {}", code);
// Get the end position of the comment.
- let line = parsed_source
+ let line = maybe_parsed_source
.unwrap()
.source()
.line_and_column_index(ignore_comment.span.hi());
@@ -1099,157 +757,9 @@ fn prepend_whitespace(content: String, line_content: Option<String>) -> String {
}
}
-/// Get LSP range from the provided start and end locations.
-fn get_range_from_location(
- start: &ast::Location,
- end: &ast::Location,
-) -> lsp::Range {
- lsp::Range {
- start: lsp::Position {
- line: (start.line - 1) as u32,
- character: start.col as u32,
- },
- end: lsp::Position {
- line: (end.line - 1) as u32,
- character: end.col as u32,
- },
- }
-}
-
-/// Narrow the range to only include the text of the specifier, excluding the
-/// quotes.
-fn narrow_range(range: lsp::Range) -> lsp::Range {
- lsp::Range {
- start: lsp::Position {
- line: range.start.line,
- character: range.start.character + 1,
- },
- end: lsp::Position {
- line: range.end.line,
- character: range.end.character - 1,
- },
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct DependencyRange {
- /// The LSP Range is inclusive of the quotes around the specifier.
- pub range: lsp::Range,
- /// The text of the specifier within the document.
- pub specifier: String,
-}
-
-impl DependencyRange {
- /// Determine if the position is within the range
- fn within(&self, position: &lsp::Position) -> bool {
- (position.line > self.range.start.line
- || position.line == self.range.start.line
- && position.character >= self.range.start.character)
- && (position.line < self.range.end.line
- || position.line == self.range.end.line
- && position.character <= self.range.end.character)
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct DependencyRanges(Vec<DependencyRange>);
-
-impl DependencyRanges {
- pub fn contains(&self, position: &lsp::Position) -> Option<DependencyRange> {
- self.0.iter().find(|r| r.within(position)).cloned()
- }
-}
-
-struct DependencyRangeCollector<'a> {
- import_ranges: DependencyRanges,
- parsed_source: &'a deno_ast::ParsedSource,
-}
-
-impl<'a> DependencyRangeCollector<'a> {
- pub fn new(parsed_source: &'a deno_ast::ParsedSource) -> Self {
- Self {
- import_ranges: DependencyRanges::default(),
- parsed_source,
- }
- }
-
- pub fn take(self) -> DependencyRanges {
- self.import_ranges
- }
-}
-
-impl<'a> Visit for DependencyRangeCollector<'a> {
- fn visit_import_decl(
- &mut self,
- node: &swc_ast::ImportDecl,
- _parent: &dyn Node,
- ) {
- let start = Location::from_pos(self.parsed_source, node.src.span.lo);
- let end = Location::from_pos(self.parsed_source, node.src.span.hi);
- self.import_ranges.0.push(DependencyRange {
- range: narrow_range(get_range_from_location(&start, &end)),
- specifier: node.src.value.to_string(),
- });
- }
-
- fn visit_named_export(
- &mut self,
- node: &swc_ast::NamedExport,
- _parent: &dyn Node,
- ) {
- if let Some(src) = &node.src {
- let start = Location::from_pos(self.parsed_source, src.span.lo);
- let end = Location::from_pos(self.parsed_source, src.span.hi);
- self.import_ranges.0.push(DependencyRange {
- range: narrow_range(get_range_from_location(&start, &end)),
- specifier: src.value.to_string(),
- });
- }
- }
-
- fn visit_export_all(
- &mut self,
- node: &swc_ast::ExportAll,
- _parent: &dyn Node,
- ) {
- let start = Location::from_pos(self.parsed_source, node.src.span.lo);
- let end = Location::from_pos(self.parsed_source, node.src.span.hi);
- self.import_ranges.0.push(DependencyRange {
- range: narrow_range(get_range_from_location(&start, &end)),
- specifier: node.src.value.to_string(),
- });
- }
-
- fn visit_ts_import_type(
- &mut self,
- node: &swc_ast::TsImportType,
- _parent: &dyn Node,
- ) {
- let start = Location::from_pos(self.parsed_source, node.arg.span.lo);
- let end = Location::from_pos(self.parsed_source, node.arg.span.hi);
- self.import_ranges.0.push(DependencyRange {
- range: narrow_range(get_range_from_location(&start, &end)),
- specifier: node.arg.value.to_string(),
- });
- }
-}
-
-/// Analyze a document for import ranges, which then can be used to identify if
-/// a particular position within the document as inside an import range.
-pub fn analyze_dependency_ranges(
- parsed_source: &deno_ast::ParsedSource,
-) -> Result<DependencyRanges, AnyError> {
- let mut collector = DependencyRangeCollector::new(parsed_source);
- parsed_source
- .module()
- .visit_with(&swc_ast::Invalid { span: DUMMY_SP }, &mut collector);
- Ok(collector.take())
-}
-
#[cfg(test)]
mod tests {
use super::*;
- use deno_core::resolve_url;
#[test]
fn test_reference_to_diagnostic() {
@@ -1338,209 +848,4 @@ mod tests {
}
);
}
-
- #[test]
- fn test_get_lint_references() {
- let specifier = resolve_url("file:///a.ts").expect("bad specifier");
- let source = "const foo = 42;";
- let parsed_module = parse_module(
- &specifier,
- SourceTextInfo::from_string(source.to_string()),
- MediaType::TypeScript,
- )
- .unwrap();
- let actual = get_lint_references(&parsed_module, None).unwrap();
-
- assert_eq!(
- actual,
- vec![Reference {
- category: Category::Lint {
- message: "`foo` is never used".to_string(),
- code: "no-unused-vars".to_string(),
- hint: Some(
- "If this is intentional, prefix it with an underscore like `_foo`"
- .to_string()
- ),
- },
- range: Range {
- start: Position {
- line: 0,
- character: 6,
- },
- end: Position {
- line: 0,
- character: 9,
- }
- }
- }]
- );
- }
-
- #[test]
- fn test_analyze_dependencies() {
- let specifier = resolve_url("file:///a.ts").expect("bad specifier");
- let source = r#"import {
- Application,
- Context,
- Router,
- Status,
- } from "https://deno.land/x/oak@v6.3.2/mod.ts";
-
- import type { Component } from "https://esm.sh/preact";
- import { h, Fragment } from "https://esm.sh/preact";
-
- // @deno-types="https://deno.land/x/types/react/index.d.ts";
- import React from "https://cdn.skypack.dev/react";
- "#;
- let parsed_module = parse_module(
- &specifier,
- SourceTextInfo::from_string(source.to_string()),
- MediaType::TypeScript,
- )
- .unwrap();
- let (actual, maybe_type) = analyze_dependencies(
- &specifier,
- MediaType::TypeScript,
- &parsed_module,
- &None,
- );
- assert!(maybe_type.is_none());
- assert_eq!(actual.len(), 3);
- assert_eq!(
- actual.get("https://cdn.skypack.dev/react").cloned(),
- Some(Dependency {
- is_dynamic: false,
- maybe_code: Some(ResolvedDependency::Resolved(
- resolve_url("https://cdn.skypack.dev/react").unwrap()
- )),
- maybe_type: Some(ResolvedDependency::Resolved(
- resolve_url("https://deno.land/x/types/react/index.d.ts").unwrap()
- )),
- maybe_code_specifier_range: Some(Range {
- start: Position {
- line: 11,
- character: 22,
- },
- end: Position {
- line: 11,
- character: 53,
- }
- }),
- maybe_type_specifier_range: Some(Range {
- start: Position {
- line: 10,
- character: 20,
- },
- end: Position {
- line: 10,
- character: 62,
- }
- })
- })
- );
- assert_eq!(
- actual.get("https://deno.land/x/oak@v6.3.2/mod.ts").cloned(),
- Some(Dependency {
- is_dynamic: false,
- maybe_code: Some(ResolvedDependency::Resolved(
- resolve_url("https://deno.land/x/oak@v6.3.2/mod.ts").unwrap()
- )),
- maybe_type: None,
- maybe_code_specifier_range: Some(Range {
- start: Position {
- line: 5,
- character: 11,
- },
- end: Position {
- line: 5,
- character: 50,
- }
- }),
- maybe_type_specifier_range: None,
- })
- );
- assert_eq!(
- actual.get("https://esm.sh/preact").cloned(),
- Some(Dependency {
- is_dynamic: false,
- maybe_code: Some(ResolvedDependency::Resolved(
- resolve_url("https://esm.sh/preact").unwrap()
- )),
- maybe_type: None,
- maybe_code_specifier_range: Some(Range {
- start: Position {
- line: 8,
- character: 32
- },
- end: Position {
- line: 8,
- character: 55
- }
- }),
- maybe_type_specifier_range: None,
- }),
- );
- }
-
- #[test]
- fn test_analyze_dependency_ranges() {
- let specifier = resolve_url("file:///a.ts").unwrap();
- let source =
- "import * as a from \"./b.ts\";\nexport * as a from \"./c.ts\";\n";
- let media_type = MediaType::TypeScript;
- let parsed_module = parse_module(
- &specifier,
- SourceTextInfo::from_string(source.to_string()),
- media_type,
- )
- .unwrap();
- let result = analyze_dependency_ranges(&parsed_module);
- assert!(result.is_ok());
- let actual = result.unwrap();
- assert_eq!(
- actual.contains(&lsp::Position {
- line: 0,
- character: 0,
- }),
- None
- );
- assert_eq!(
- actual.contains(&lsp::Position {
- line: 0,
- character: 22,
- }),
- Some(DependencyRange {
- range: lsp::Range {
- start: lsp::Position {
- line: 0,
- character: 20,
- },
- end: lsp::Position {
- line: 0,
- character: 26,
- },
- },
- specifier: "./b.ts".to_string(),
- })
- );
- assert_eq!(
- actual.contains(&lsp::Position {
- line: 1,
- character: 22,
- }),
- Some(DependencyRange {
- range: lsp::Range {
- start: lsp::Position {
- line: 1,
- character: 20,
- },
- end: lsp::Position {
- line: 1,
- character: 26,
- },
- },
- specifier: "./c.ts".to_string(),
- })
- );
- }
}
diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs
index 6755f50d5..97ad8f260 100644
--- a/cli/lsp/code_lens.rs
+++ b/cli/lsp/code_lens.rs
@@ -25,6 +25,7 @@ use regex::Regex;
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
+use std::sync::Arc;
lazy_static::lazy_static! {
static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap();
@@ -61,18 +62,15 @@ fn span_to_range(span: &Span, parsed_source: &ParsedSource) -> lsp::Range {
}
}
-struct DenoTestCollector<'a> {
+struct DenoTestCollector {
code_lenses: Vec<lsp::CodeLens>,
- parsed_source: &'a ParsedSource,
+ parsed_source: ParsedSource,
specifier: ModuleSpecifier,
test_vars: HashSet<String>,
}
-impl<'a> DenoTestCollector<'a> {
- pub fn new(
- specifier: ModuleSpecifier,
- parsed_source: &'a ParsedSource,
- ) -> Self {
+impl DenoTestCollector {
+ pub fn new(specifier: ModuleSpecifier, parsed_source: ParsedSource) -> Self {
Self {
code_lenses: Vec::new(),
parsed_source,
@@ -82,7 +80,7 @@ impl<'a> DenoTestCollector<'a> {
}
fn add_code_lens<N: AsRef<str>>(&mut self, name: N, span: &Span) {
- let range = span_to_range(span, self.parsed_source);
+ let range = span_to_range(span, &self.parsed_source);
self.code_lenses.push(lsp::CodeLens {
range,
command: Some(lsp::Command {
@@ -130,7 +128,7 @@ impl<'a> DenoTestCollector<'a> {
}
}
-impl<'a> Visit for DenoTestCollector<'a> {
+impl Visit for DenoTestCollector {
fn visit_call_expr(&mut self, node: &ast::CallExpr, _parent: &dyn Node) {
if let ast::ExprOrSuper::Expr(callee_expr) = &node.callee {
match callee_expr.as_ref() {
@@ -238,7 +236,7 @@ async fn resolve_implementation_code_lens(
let implementation_specifier =
resolve_url(&implementation.document_span.file_name)?;
let implementation_location =
- implementation.to_location(&line_index, language_server);
+ implementation.to_location(line_index.clone(), language_server);
if !(implementation_specifier == data.specifier
&& implementation_location.range.start == code_lens.range.start)
{
@@ -311,7 +309,7 @@ async fn resolve_references_code_lens(
resolve_url(&reference.document_span.file_name)?;
let line_index =
language_server.get_line_index(reference_specifier).await?;
- locations.push(reference.to_location(&line_index, language_server));
+ locations.push(reference.to_location(line_index, language_server));
}
let command = if !locations.is_empty() {
let title = if locations.len() > 1 {
@@ -372,9 +370,9 @@ pub(crate) async fn resolve_code_lens(
pub(crate) async fn collect(
specifier: &ModuleSpecifier,
- parsed_source: Option<&ParsedSource>,
+ parsed_source: Option<ParsedSource>,
config: &Config,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
navigation_tree: &NavigationTree,
) -> Result<Vec<lsp::CodeLens>, AnyError> {
let mut code_lenses = collect_test(specifier, parsed_source, config)?;
@@ -393,13 +391,13 @@ pub(crate) async fn collect(
fn collect_test(
specifier: &ModuleSpecifier,
- parsed_source: Option<&ParsedSource>,
+ parsed_source: Option<ParsedSource>,
config: &Config,
) -> Result<Vec<lsp::CodeLens>, AnyError> {
if config.specifier_code_lens_test(specifier) {
if let Some(parsed_source) = parsed_source {
let mut collector =
- DenoTestCollector::new(specifier.clone(), parsed_source);
+ DenoTestCollector::new(specifier.clone(), parsed_source.clone());
parsed_source.module().visit_with(
&ast::Invalid {
span: deno_ast::swc::common::DUMMY_SP,
@@ -416,7 +414,7 @@ fn collect_test(
async fn collect_tsc(
specifier: &ModuleSpecifier,
workspace_settings: &WorkspaceSettings,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
navigation_tree: &NavigationTree,
) -> Result<Vec<lsp::CodeLens>, AnyError> {
let code_lenses = Rc::new(RefCell::new(Vec::new()));
@@ -428,7 +426,11 @@ async fn collect_tsc(
let source = CodeLensSource::Implementations;
match i.kind {
tsc::ScriptElementKind::InterfaceElement => {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
tsc::ScriptElementKind::ClassElement
| tsc::ScriptElementKind::MemberFunctionElement
@@ -436,7 +438,11 @@ async fn collect_tsc(
| tsc::ScriptElementKind::MemberGetAccessorElement
| tsc::ScriptElementKind::MemberSetAccessorElement => {
if ABSTRACT_MODIFIER.is_match(&i.kind_modifiers) {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
}
_ => (),
@@ -448,31 +454,51 @@ async fn collect_tsc(
let source = CodeLensSource::References;
if let Some(parent) = &mp {
if parent.kind == tsc::ScriptElementKind::EnumElement {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
}
match i.kind {
tsc::ScriptElementKind::FunctionElement => {
if workspace_settings.code_lens.references_all_functions {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
}
tsc::ScriptElementKind::ConstElement
| tsc::ScriptElementKind::LetElement
| tsc::ScriptElementKind::VariableElement => {
if EXPORT_MODIFIER.is_match(&i.kind_modifiers) {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
}
tsc::ScriptElementKind::ClassElement => {
if i.text != "<class>" {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
}
tsc::ScriptElementKind::InterfaceElement
| tsc::ScriptElementKind::TypeElement
| tsc::ScriptElementKind::EnumElement => {
- code_lenses.push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
tsc::ScriptElementKind::LocalFunctionElement
| tsc::ScriptElementKind::MemberGetAccessorElement
@@ -485,8 +511,11 @@ async fn collect_tsc(
tsc::ScriptElementKind::ClassElement
| tsc::ScriptElementKind::InterfaceElement
| tsc::ScriptElementKind::TypeElement => {
- code_lenses
- .push(i.to_code_lens(line_index, specifier, &source));
+ code_lenses.push(i.to_code_lens(
+ line_index.clone(),
+ specifier,
+ &source,
+ ));
}
_ => (),
}
@@ -510,21 +539,28 @@ mod tests {
#[test]
fn test_deno_test_collector() {
let specifier = resolve_url("https://deno.land/x/mod.ts").unwrap();
- let source = r#"
+ let source = Arc::new(
+ r#"
Deno.test({
name: "test a",
fn() {}
});
Deno.test("test b", function anotherTest() {});
- "#;
- let parsed_module = crate::lsp::analysis::parse_module(
- &specifier,
- SourceTextInfo::from_string(source.to_string()),
- MediaType::TypeScript,
- )
+ "#
+ .to_string(),
+ );
+ let parsed_module = deno_ast::parse_module(deno_ast::ParseParams {
+ specifier: specifier.to_string(),
+ source: SourceTextInfo::new(source),
+ media_type: MediaType::TypeScript,
+ capture_tokens: true,
+ scope_analysis: true,
+ maybe_syntax: None,
+ })
.unwrap();
- let mut collector = DenoTestCollector::new(specifier, &parsed_module);
+ let mut collector =
+ DenoTestCollector::new(specifier, parsed_module.clone());
parsed_module.module().visit_with(
&ast::Invalid {
span: deno_ast::swc::common::DUMMY_SP,
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 2fa526f6f..4931b5560 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -1,6 +1,5 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-use super::analysis;
use super::language_server;
use super::lsp_custom;
use super::tsc;
@@ -85,21 +84,34 @@ async fn check_auto_config_registry(
}
}
+/// Ranges from the graph for specifiers include the leading and trailing quote,
+/// which we want to ignore when replacing text.
+fn to_narrow_lsp_range(range: &deno_graph::Range) -> lsp::Range {
+ lsp::Range {
+ start: lsp::Position {
+ line: range.start.line as u32,
+ character: (range.start.character + 1) as u32,
+ },
+ end: lsp::Position {
+ line: range.end.line as u32,
+ character: (range.end.character - 1) as u32,
+ },
+ }
+}
+
/// Given a specifier, a position, and a snapshot, optionally return a
/// completion response, which will be valid import completions for the specific
/// context.
-pub async fn get_import_completions(
+pub(crate) async fn get_import_completions(
specifier: &ModuleSpecifier,
position: &lsp::Position,
state_snapshot: &language_server::StateSnapshot,
client: lspower::Client,
) -> Option<lsp::CompletionResponse> {
- let analysis::DependencyRange {
- range,
- specifier: text,
- } = state_snapshot
+ let (text, _, range) = state_snapshot
.documents
- .is_specifier_position(specifier, position)?;
+ .get_maybe_dependency(specifier, position)?;
+ let range = to_narrow_lsp_range(&range);
// completions for local relative modules
if text.starts_with("./") || text.starts_with("../") {
Some(lsp::CompletionResponse::List(lsp::CompletionList {
@@ -258,7 +270,7 @@ fn get_workspace_completions(
range: &lsp::Range,
state_snapshot: &language_server::StateSnapshot,
) -> Vec<lsp::CompletionItem> {
- let workspace_specifiers = state_snapshot.sources.specifiers();
+ let workspace_specifiers = state_snapshot.documents.specifiers(false, true);
let specifier_strings =
get_relative_specifiers(specifier, workspace_specifiers);
specifier_strings
@@ -399,11 +411,8 @@ fn relative_specifier(
mod tests {
use super::*;
use crate::http_cache::HttpCache;
- use crate::lsp::analysis;
- use crate::lsp::documents::DocumentCache;
+ use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId;
- use crate::lsp::sources::Sources;
- use deno_ast::MediaType;
use deno_core::resolve_url;
use std::collections::HashMap;
use std::path::Path;
@@ -415,37 +424,17 @@ mod tests {
source_fixtures: &[(&str, &str)],
location: &Path,
) -> language_server::StateSnapshot {
- let mut documents = DocumentCache::default();
+ let documents = Documents::new(location);
for (specifier, source, version, language_id) in fixtures {
let specifier =
resolve_url(specifier).expect("failed to create specifier");
documents.open(
specifier.clone(),
*version,
- *language_id,
+ language_id.clone(),
Arc::new(source.to_string()),
);
- let media_type = MediaType::from(&specifier);
- let parsed_module = documents
- .get(&specifier)
- .unwrap()
- .source()
- .module()
- .map(|r| r.as_ref())
- .unwrap()
- .unwrap();
- let (deps, _) = analysis::analyze_dependencies(
- &specifier,
- media_type,
- parsed_module,
- &None,
- );
- let dep_ranges = analysis::analyze_dependency_ranges(parsed_module).ok();
- documents
- .set_dependencies(&specifier, Some(deps), dep_ranges)
- .unwrap();
}
- let sources = Sources::new(location);
let http_cache = HttpCache::new(location);
for (specifier, source) in source_fixtures {
let specifier =
@@ -454,13 +443,12 @@ mod tests {
.set(&specifier, HashMap::default(), source.as_bytes())
.expect("could not cache file");
assert!(
- sources.get_source(&specifier).is_some(),
+ documents.content(&specifier).is_some(),
"source could not be setup"
);
}
language_server::StateSnapshot {
documents,
- sources,
..Default::default()
}
}
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 4bae048c0..698fcd744 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -1,15 +1,14 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::analysis;
-use super::documents::DocumentCache;
+use super::documents;
+use super::documents::Documents;
use super::language_server;
-use super::sources::Sources;
use super::tsc;
use crate::diagnostics;
use crate::tokio_util::create_basic_runtime;
-use analysis::ResolvedDependency;
use deno_core::error::anyhow;
use deno_core::error::AnyError;
use deno_core::resolve_url;
@@ -90,21 +89,13 @@ impl DiagnosticCollection {
}
}
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub(crate) struct DiagnosticsServer {
channel: Option<mpsc::UnboundedSender<()>>,
collection: Arc<Mutex<DiagnosticCollection>>,
}
impl DiagnosticsServer {
- pub(crate) fn new() -> Self {
- let collection = Arc::new(Mutex::new(DiagnosticCollection::default()));
- Self {
- channel: None,
- collection,
- }
- }
-
pub(crate) async fn get(
&self,
specifier: &ModuleSpecifier,
@@ -318,24 +309,17 @@ async fn generate_lint_diagnostics(
tokio::task::spawn(async move {
let mut diagnostics_vec = Vec::new();
if workspace_settings.lint {
- for specifier in documents.open_specifiers() {
- if !documents.is_diagnosable(specifier) {
- continue;
- }
- let version = documents.version(specifier);
+ for specifier in documents.specifiers(true, true) {
+ let version = documents.lsp_version(&specifier);
let current_version = collection
.lock()
.await
- .get_version(specifier, &DiagnosticSource::DenoLint);
+ .get_version(&specifier, &DiagnosticSource::DenoLint);
if version != current_version {
- let module = documents
- .get(specifier)
- .map(|d| d.source().module())
- .flatten();
- let diagnostics = match module {
- Some(Ok(module)) => {
+ let diagnostics = match documents.parsed_source(&specifier) {
+ Some(Ok(parsed_source)) => {
if let Ok(references) = analysis::get_lint_references(
- module,
+ &parsed_source,
maybe_lint_config.as_ref(),
) {
references
@@ -372,18 +356,14 @@ async fn generate_ts_diagnostics(
let collection = collection.lock().await;
snapshot
.documents
- .open_specifiers()
+ .specifiers(true, true)
.iter()
- .filter_map(|&s| {
- if snapshot.documents.is_diagnosable(s) {
- let version = snapshot.documents.version(s);
- let current_version =
- collection.get_version(s, &DiagnosticSource::TypeScript);
- if version != current_version {
- Some(s.clone())
- } else {
- None
- }
+ .filter_map(|s| {
+ let version = snapshot.documents.lsp_version(s);
+ let current_version =
+ collection.get_version(s, &DiagnosticSource::TypeScript);
+ if version != current_version {
+ Some(s.clone())
} else {
None
}
@@ -396,7 +376,7 @@ async fn generate_ts_diagnostics(
ts_server.request(snapshot.clone(), req).await?;
for (specifier_str, ts_diagnostics) in ts_diagnostics_map {
let specifier = resolve_url(&specifier_str)?;
- let version = snapshot.documents.version(&specifier);
+ let version = snapshot.documents.lsp_version(&specifier);
diagnostics_vec.push((
specifier,
version,
@@ -407,61 +387,76 @@ async fn generate_ts_diagnostics(
Ok(diagnostics_vec)
}
+fn resolution_error_as_code(
+ err: &deno_graph::ResolutionError,
+) -> lsp::NumberOrString {
+ use deno_graph::ResolutionError;
+ use deno_graph::SpecifierError;
+
+ match err {
+ ResolutionError::InvalidDowngrade(_, _) => {
+ lsp::NumberOrString::String("invalid-downgrade".to_string())
+ }
+ ResolutionError::InvalidLocalImport(_, _) => {
+ lsp::NumberOrString::String("invalid-local-import".to_string())
+ }
+ ResolutionError::InvalidSpecifier(err, _) => match err {
+ SpecifierError::ImportPrefixMissing(_, _) => {
+ lsp::NumberOrString::String("import-prefix-missing".to_string())
+ }
+ SpecifierError::InvalidUrl(_) => {
+ lsp::NumberOrString::String("invalid-url".to_string())
+ }
+ },
+ ResolutionError::ResolverError(_, _, _) => {
+ lsp::NumberOrString::String("resolver-error".to_string())
+ }
+ }
+}
+
fn diagnose_dependency(
diagnostics: &mut Vec<lsp::Diagnostic>,
- documents: &DocumentCache,
- sources: &Sources,
- maybe_dependency: &Option<ResolvedDependency>,
- maybe_range: &Option<lsp::Range>,
+ documents: &Documents,
+ resolved: &deno_graph::Resolved,
) {
- if let (Some(dep), Some(range)) = (maybe_dependency, *maybe_range) {
- match dep {
- analysis::ResolvedDependency::Err(err) => {
+ match resolved {
+ Some(Ok((specifier, range))) => {
+ if !documents.contains_specifier(specifier) {
+ let (code, message) = match specifier.scheme() {
+ "file" => (Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier)),
+ "data" => (Some(lsp::NumberOrString::String("no-cache-data".to_string())), "Uncached data URL.".to_string()),
+ "blob" => (Some(lsp::NumberOrString::String("no-cache-blob".to_string())), "Uncached blob URL.".to_string()),
+ _ => (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Uncached or missing remote URL: \"{}\".", specifier)),
+ };
diagnostics.push(lsp::Diagnostic {
- range,
+ range: documents::to_lsp_range(range),
severity: Some(lsp::DiagnosticSeverity::Error),
- code: Some(err.as_code()),
- code_description: None,
+ code,
source: Some("deno".to_string()),
- message: err.to_string(),
- related_information: None,
- tags: None,
- data: None,
+ message,
+ data: Some(json!({ "specifier": specifier })),
+ ..Default::default()
+ });
+ } else if let Some(message) = documents.maybe_warning(specifier) {
+ diagnostics.push(lsp::Diagnostic {
+ range: documents::to_lsp_range(range),
+ severity: Some(lsp::DiagnosticSeverity::Warning),
+ code: Some(lsp::NumberOrString::String("deno-warn".to_string())),
+ source: Some("deno".to_string()),
+ message,
+ ..Default::default()
})
}
- analysis::ResolvedDependency::Resolved(specifier) => {
- if !(documents.contains_key(specifier)
- || sources.contains_key(specifier))
- {
- let (code, message) = match specifier.scheme() {
- "file" => (Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier)),
- "data" => (Some(lsp::NumberOrString::String("no-cache-data".to_string())), "Uncached data URL.".to_string()),
- "blob" => (Some(lsp::NumberOrString::String("no-cache-blob".to_string())), "Uncached blob URL.".to_string()),
- _ => (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Uncached or missing remote URL: \"{}\".", specifier)),
- };
- diagnostics.push(lsp::Diagnostic {
- range,
- severity: Some(lsp::DiagnosticSeverity::Error),
- code,
- source: Some("deno".to_string()),
- message,
- data: Some(json!({ "specifier": specifier })),
- ..Default::default()
- });
- } else if sources.contains_key(specifier) {
- if let Some(message) = sources.get_maybe_warning(specifier) {
- diagnostics.push(lsp::Diagnostic {
- range,
- severity: Some(lsp::DiagnosticSeverity::Warning),
- code: Some(lsp::NumberOrString::String("deno-warn".to_string())),
- source: Some("deno".to_string()),
- message,
- ..Default::default()
- })
- }
- }
- }
}
+ Some(Err(err)) => diagnostics.push(lsp::Diagnostic {
+ range: documents::to_lsp_range(err.range()),
+ severity: Some(lsp::DiagnosticSeverity::Error),
+ code: Some(resolution_error_as_code(err)),
+ source: Some("deno".to_string()),
+ message: err.to_string(),
+ ..Default::default()
+ }),
+ _ => (),
}
}
@@ -473,36 +468,31 @@ async fn generate_deps_diagnostics(
) -> Result<DiagnosticVec, AnyError> {
let config = snapshot.config.clone();
let documents = snapshot.documents.clone();
- let sources = snapshot.sources.clone();
tokio::task::spawn(async move {
let mut diagnostics_vec = Vec::new();
- for specifier in documents.open_specifiers() {
- if !config.specifier_enabled(specifier) {
+ for specifier in documents.specifiers(true, true) {
+ if !config.specifier_enabled(&specifier) {
continue;
}
- let version = documents.version(specifier);
+ let version = documents.lsp_version(&specifier);
let current_version = collection
.lock()
.await
- .get_version(specifier, &DiagnosticSource::Deno);
+ .get_version(&specifier, &DiagnosticSource::Deno);
if version != current_version {
let mut diagnostics = Vec::new();
- if let Some(dependencies) = documents.dependencies(specifier) {
+ if let Some(dependencies) = documents.dependencies(&specifier) {
for (_, dependency) in dependencies {
diagnose_dependency(
&mut diagnostics,
&documents,
- &sources,
&dependency.maybe_code,
- &dependency.maybe_code_specifier_range,
);
diagnose_dependency(
&mut diagnostics,
&documents,
- &sources,
&dependency.maybe_type,
- &dependency.maybe_type_specifier_range,
);
}
}
@@ -544,7 +534,7 @@ async fn publish_diagnostics(
.extend(collection.get(&specifier, DiagnosticSource::Deno).cloned());
}
let uri = specifier.clone();
- let version = snapshot.documents.version(&specifier);
+ let version = snapshot.documents.lsp_version(&specifier);
client.publish_diagnostics(uri, diagnostics, version).await;
}
}
@@ -627,3 +617,79 @@ async fn update_diagnostics(
tokio::join!(lint, ts, deps);
snapshot.performance.measure(mark);
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::lsp::config::ConfigSnapshot;
+ use crate::lsp::config::Settings;
+ use crate::lsp::config::WorkspaceSettings;
+ use crate::lsp::documents::LanguageId;
+ use crate::lsp::language_server::StateSnapshot;
+ use std::path::Path;
+ use std::path::PathBuf;
+ use tempfile::TempDir;
+
+ fn mock_state_snapshot(
+ fixtures: &[(&str, &str, i32, LanguageId)],
+ location: &Path,
+ ) -> StateSnapshot {
+ let documents = Documents::new(location);
+ for (specifier, source, version, language_id) in fixtures {
+ let specifier =
+ resolve_url(specifier).expect("failed to create specifier");
+ documents.open(
+ specifier.clone(),
+ *version,
+ language_id.clone(),
+ Arc::new(source.to_string()),
+ );
+ }
+ let config = ConfigSnapshot {
+ settings: Settings {
+ workspace: WorkspaceSettings {
+ enable: true,
+ lint: true,
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+ StateSnapshot {
+ config,
+ documents,
+ ..Default::default()
+ }
+ }
+
+ fn setup(
+ sources: &[(&str, &str, i32, LanguageId)],
+ ) -> (StateSnapshot, Arc<Mutex<DiagnosticCollection>>, PathBuf) {
+ let temp_dir = TempDir::new().expect("could not create temp dir");
+ let location = temp_dir.path().join("deps");
+ let state_snapshot = mock_state_snapshot(sources, &location);
+ let collection = Arc::new(Mutex::new(DiagnosticCollection::default()));
+ (state_snapshot, collection, location)
+ }
+
+ #[tokio::test]
+ async fn test_generate_lint_diagnostics() {
+ let (snapshot, collection, _) = setup(&[(
+ "file:///a.ts",
+ r#"import * as b from "./b.ts";
+
+let a = "a";
+console.log(a);
+"#,
+ 1,
+ LanguageId::TypeScript,
+ )]);
+ let result = generate_lint_diagnostics(&snapshot, collection).await;
+ assert!(result.is_ok());
+ let diagnostics = result.unwrap();
+ assert_eq!(diagnostics.len(), 1);
+ let (_, _, diagnostics) = &diagnostics[0];
+ assert_eq!(diagnostics.len(), 2);
+ }
+}
diff --git a/cli/lsp/document_source.rs b/cli/lsp/document_source.rs
deleted file mode 100644
index c2bef884e..000000000
--- a/cli/lsp/document_source.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-use deno_ast::Diagnostic;
-use deno_ast::MediaType;
-use deno_ast::ParsedSource;
-use deno_ast::SourceTextInfo;
-use deno_core::ModuleSpecifier;
-use once_cell::sync::OnceCell;
-use std::sync::Arc;
-
-use super::analysis;
-use super::text::LineIndex;
-
-#[derive(Debug)]
-struct DocumentSourceInner {
- specifier: ModuleSpecifier,
- media_type: MediaType,
- text_info: SourceTextInfo,
- parsed_module: OnceCell<Result<ParsedSource, Diagnostic>>,
- line_index: LineIndex,
-}
-
-/// Immutable information about a document.
-#[derive(Debug, Clone)]
-pub struct DocumentSource {
- inner: Arc<DocumentSourceInner>,
-}
-
-impl DocumentSource {
- pub fn new(
- specifier: &ModuleSpecifier,
- media_type: MediaType,
- text: Arc<String>,
- line_index: LineIndex,
- ) -> Self {
- Self {
- inner: Arc::new(DocumentSourceInner {
- specifier: specifier.clone(),
- media_type,
- text_info: SourceTextInfo::new(text),
- parsed_module: OnceCell::new(),
- line_index,
- }),
- }
- }
-
- pub fn text_info(&self) -> &SourceTextInfo {
- &self.inner.text_info
- }
-
- pub fn line_index(&self) -> &LineIndex {
- &self.inner.line_index
- }
-
- pub fn module(&self) -> Option<&Result<ParsedSource, Diagnostic>> {
- let is_parsable = matches!(
- self.inner.media_type,
- MediaType::JavaScript
- | MediaType::Jsx
- | MediaType::TypeScript
- | MediaType::Tsx
- | MediaType::Dts,
- );
- if is_parsable {
- // lazily parse the module
- Some(self.inner.parsed_module.get_or_init(|| {
- analysis::parse_module(
- &self.inner.specifier,
- self.inner.text_info.clone(),
- self.inner.media_type,
- )
- }))
- } else {
- None
- }
- }
-}
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index 3855150e7..640a48f14 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -1,25 +1,75 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-use super::analysis;
-use super::document_source::DocumentSource;
+use super::resolver::ImportMapResolver;
use super::text::LineIndex;
use super::tsc;
+use crate::file_fetcher::get_source_from_bytes;
+use crate::file_fetcher::map_content_type;
+use crate::file_fetcher::SUPPORTED_SCHEMES;
+use crate::http_cache;
+use crate::http_cache::HttpCache;
+use crate::text_encoding;
+
use deno_ast::MediaType;
+use deno_ast::SourceTextInfo;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
+use deno_core::parking_lot::Mutex;
+use deno_core::url;
use deno_core::ModuleSpecifier;
use lspower::lsp;
use std::collections::HashMap;
use std::collections::HashSet;
+use std::fs;
use std::ops::Range;
+use std::path::Path;
+use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
+use std::time::SystemTime;
+
+lazy_static::lazy_static! {
+ static ref JS_HEADERS: HashMap<String, String> = ([
+ ("content-type".to_string(), "application/javascript".to_string())
+ ]).iter().cloned().collect();
+ static ref JSX_HEADERS: HashMap<String, String> = ([
+ ("content-type".to_string(), "text/jsx".to_string())
+ ]).iter().cloned().collect();
+ static ref TS_HEADERS: HashMap<String, String> = ([
+ ("content-type".to_string(), "application/typescript".to_string())
+ ]).iter().cloned().collect();
+ static ref TSX_HEADERS: HashMap<String, String> = ([
+ ("content-type".to_string(), "text/tsx".to_string())
+ ]).iter().cloned().collect();
+}
-/// A representation of the language id sent from the LSP client, which is used
-/// to determine how the document is handled within the language server.
-#[derive(Debug, Clone, PartialEq, Eq, Copy)]
-pub enum LanguageId {
+/// The default parser from `deno_graph` does not include the configuration
+/// options we require here, and so implementing an empty struct that provides
+/// the trait.
+#[derive(Debug, Default)]
+struct SourceParser {}
+
+impl deno_graph::SourceParser for SourceParser {
+ fn parse_module(
+ &self,
+ specifier: &ModuleSpecifier,
+ source: Arc<String>,
+ media_type: MediaType,
+ ) -> Result<deno_ast::ParsedSource, deno_ast::Diagnostic> {
+ deno_ast::parse_module(deno_ast::ParseParams {
+ specifier: specifier.to_string(),
+ source: SourceTextInfo::new(source),
+ media_type,
+ capture_tokens: true,
+ scope_analysis: true,
+ maybe_syntax: None,
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) enum LanguageId {
JavaScript,
Jsx,
TypeScript,
@@ -30,17 +80,34 @@ pub enum LanguageId {
Unknown,
}
+impl LanguageId {
+ fn as_headers(&self) -> Option<&HashMap<String, String>> {
+ match self {
+ Self::JavaScript => Some(&JS_HEADERS),
+ Self::Jsx => Some(&JSX_HEADERS),
+ Self::TypeScript => Some(&TS_HEADERS),
+ Self::Tsx => Some(&TSX_HEADERS),
+ _ => None,
+ }
+ }
+
+ fn is_diagnosable(&self) -> bool {
+ matches!(
+ self,
+ Self::JavaScript | Self::Jsx | Self::TypeScript | Self::Tsx
+ )
+ }
+}
+
impl FromStr for LanguageId {
type Err = AnyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"javascript" => Ok(Self::JavaScript),
- "javascriptreact" => Ok(Self::Jsx),
- "jsx" => Ok(Self::Jsx),
+ "javascriptreact" | "jsx" => Ok(Self::Jsx),
"typescript" => Ok(Self::TypeScript),
- "typescriptreact" => Ok(Self::Tsx),
- "tsx" => Ok(Self::Tsx),
+ "typescriptreact" | "tsx" => Ok(Self::Tsx),
"json" => Ok(Self::Json),
"jsonc" => Ok(Self::JsonC),
"markdown" => Ok(Self::Markdown),
@@ -49,21 +116,6 @@ impl FromStr for LanguageId {
}
}
-impl<'a> From<&'a LanguageId> for MediaType {
- fn from(id: &'a LanguageId) -> MediaType {
- match id {
- LanguageId::JavaScript => MediaType::JavaScript,
- LanguageId::Json => MediaType::Json,
- LanguageId::JsonC => MediaType::Json,
- LanguageId::Jsx => MediaType::Jsx,
- LanguageId::Markdown => MediaType::Unknown,
- LanguageId::Tsx => MediaType::Tsx,
- LanguageId::TypeScript => MediaType::TypeScript,
- LanguageId::Unknown => MediaType::Unknown,
- }
- }
-}
-
#[derive(Debug, PartialEq, Eq)]
enum IndexValid {
All,
@@ -79,52 +131,70 @@ impl IndexValid {
}
}
-#[derive(Debug, Clone)]
-pub struct DocumentData {
- source: DocumentSource,
- dependencies: Option<HashMap<String, analysis::Dependency>>,
- dependency_ranges: Option<analysis::DependencyRanges>,
- pub(crate) language_id: LanguageId,
- maybe_navigation_tree: Option<tsc::NavigationTree>,
+#[derive(Debug)]
+pub(crate) struct Document {
+ line_index: Arc<LineIndex>,
+ maybe_language_id: Option<LanguageId>,
+ maybe_lsp_version: Option<i32>,
+ maybe_module:
+ Option<Result<deno_graph::Module, deno_graph::ModuleGraphError>>,
+ maybe_navigation_tree: Option<Arc<tsc::NavigationTree>>,
+ maybe_warning: Option<String>,
+ source: SourceTextInfo,
specifier: ModuleSpecifier,
- version: Option<i32>,
+ version: String,
}
-impl DocumentData {
- pub fn new(
+impl Document {
+ fn new(
specifier: ModuleSpecifier,
- version: i32,
- language_id: LanguageId,
- source_text: Arc<String>,
+ version: String,
+ maybe_headers: Option<&HashMap<String, String>>,
+ content: Arc<String>,
+ maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
) -> Self {
- let line_index = LineIndex::new(&source_text);
+ let maybe_warning = maybe_headers
+ .map(|h| h.get("x-deno-warning").cloned())
+ .flatten();
+ let parser = SourceParser::default();
+ // we only ever do `Document::new` on on disk resources that are supposed to
+ // be diagnosable, unlike `Document::open`, so it is safe to unconditionally
+ // parse the module.
+ let maybe_module = Some(deno_graph::parse_module(
+ &specifier,
+ maybe_headers,
+ content.clone(),
+ maybe_resolver,
+ Some(&parser),
+ ));
+ let source = SourceTextInfo::new(content);
+ let line_index = Arc::new(LineIndex::new(source.text_str()));
Self {
- source: DocumentSource::new(
- &specifier,
- MediaType::from(&language_id),
- source_text,
- line_index,
- ),
- dependencies: None,
- dependency_ranges: None,
- language_id,
+ line_index,
+ maybe_language_id: None,
+ maybe_lsp_version: None,
+ maybe_module,
maybe_navigation_tree: None,
+ maybe_warning,
+ source,
specifier,
- version: Some(version),
+ version,
}
}
- pub fn apply_content_changes(
+ fn change(
&mut self,
- content_changes: Vec<lsp::TextDocumentContentChangeEvent>,
+ version: i32,
+ changes: Vec<lsp::TextDocumentContentChangeEvent>,
+ maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
) -> Result<(), AnyError> {
- let mut content = self.source.text_info().text_str().to_string();
- let mut line_index = self.source.line_index().clone();
+ let mut content = self.source.text_str().to_string();
+ let mut line_index = self.line_index.clone();
let mut index_valid = IndexValid::All;
- for change in content_changes {
+ for change in changes {
if let Some(range) = change.range {
if !index_valid.covers(range.start.line) {
- line_index = LineIndex::new(&content);
+ line_index = Arc::new(LineIndex::new(&content));
}
index_valid = IndexValid::UpTo(range.start.line);
let range = line_index.get_text_range(range)?;
@@ -134,446 +204,949 @@ impl DocumentData {
index_valid = IndexValid::UpTo(0);
}
}
- let line_index = if index_valid == IndexValid::All {
+ let content = Arc::new(content);
+ if self
+ .maybe_language_id
+ .as_ref()
+ .map(|li| li.is_diagnosable())
+ .unwrap_or(false)
+ {
+ let maybe_headers = self
+ .maybe_language_id
+ .as_ref()
+ .map(|li| li.as_headers())
+ .flatten();
+ let parser = SourceParser::default();
+ self.maybe_module = Some(deno_graph::parse_module(
+ &self.specifier,
+ maybe_headers,
+ content.clone(),
+ maybe_resolver,
+ Some(&parser),
+ ));
+ } else {
+ self.maybe_module = None;
+ }
+ self.source = SourceTextInfo::new(content);
+ self.line_index = if index_valid == IndexValid::All {
line_index
} else {
- LineIndex::new(&content)
+ Arc::new(LineIndex::new(self.source.text_str()))
};
- self.source = DocumentSource::new(
- &self.specifier,
- MediaType::from(&self.language_id),
- Arc::new(content),
- line_index,
- );
+ self.maybe_lsp_version = Some(version);
self.maybe_navigation_tree = None;
Ok(())
}
- pub fn source(&self) -> &DocumentSource {
- &self.source
+ fn close(&mut self) {
+ self.maybe_lsp_version = None;
+ self.maybe_language_id = None;
}
- /// Determines if a position within the document is within a dependency range
- /// and if so, returns the range with the text of the specifier.
- fn is_specifier_position(
- &self,
- position: &lsp::Position,
- ) -> Option<analysis::DependencyRange> {
- let import_ranges = self.dependency_ranges.as_ref()?;
- import_ranges.contains(position)
+ fn content(&self) -> Arc<String> {
+ self.source.text()
+ }
+
+ fn is_diagnosable(&self) -> bool {
+ matches!(
+ self.media_type(),
+ MediaType::JavaScript
+ | MediaType::Jsx
+ | MediaType::TypeScript
+ | MediaType::Tsx
+ | MediaType::Dts
+ )
+ }
+
+ fn is_open(&self) -> bool {
+ self.maybe_lsp_version.is_some()
+ }
+
+ fn media_type(&self) -> MediaType {
+ if let Some(Ok(module)) = &self.maybe_module {
+ module.media_type
+ } else {
+ MediaType::from(&self.specifier)
+ }
+ }
+
+ fn open(
+ specifier: ModuleSpecifier,
+ version: i32,
+ language_id: LanguageId,
+ content: Arc<String>,
+ maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
+ ) -> Self {
+ let maybe_headers = language_id.as_headers();
+ let parser = SourceParser::default();
+ let maybe_module = if language_id.is_diagnosable() {
+ Some(deno_graph::parse_module(
+ &specifier,
+ maybe_headers,
+ content.clone(),
+ maybe_resolver,
+ Some(&parser),
+ ))
+ } else {
+ None
+ };
+ let source = SourceTextInfo::new(content);
+ let line_index = Arc::new(LineIndex::new(source.text_str()));
+ Self {
+ line_index,
+ maybe_language_id: Some(language_id),
+ maybe_lsp_version: Some(version),
+ maybe_module,
+ maybe_navigation_tree: None,
+ maybe_warning: None,
+ source,
+ specifier,
+ version: "1".to_string(),
+ }
}
}
-#[derive(Debug, Clone, Default)]
-pub struct DocumentCache {
- dependents_graph: HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>>,
- pub(crate) docs: HashMap<ModuleSpecifier, DocumentData>,
+pub(crate) fn to_hover_text(
+ result: &Result<
+ (ModuleSpecifier, deno_graph::Range),
+ deno_graph::ResolutionError,
+ >,
+) -> String {
+ match result {
+ Ok((specifier, _)) => match specifier.scheme() {
+ "data" => "_(a data url)_".to_string(),
+ "blob" => "_(a blob url)_".to_string(),
+ _ => format!(
+ "{}&#8203;{}",
+ specifier[..url::Position::AfterScheme].to_string(),
+ specifier[url::Position::AfterScheme..].to_string()
+ ),
+ },
+ Err(_) => "_[errored]_".to_string(),
+ }
}
-impl DocumentCache {
- /// Calculate a graph of dependents and set it on the structure.
+pub(crate) fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range {
+ lsp::Range {
+ start: lsp::Position {
+ line: range.start.line as u32,
+ character: range.start.character as u32,
+ },
+ end: lsp::Position {
+ line: range.end.line as u32,
+ character: range.end.character as u32,
+ },
+ }
+}
+
+/// Recurse and collect specifiers that appear in the dependent map.
+fn recurse_dependents(
+ specifier: &ModuleSpecifier,
+ map: &HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>>,
+ dependents: &mut HashSet<ModuleSpecifier>,
+) {
+ if let Some(deps) = map.get(specifier) {
+ for dep in deps {
+ if !dependents.contains(dep) {
+ dependents.insert(dep.clone());
+ recurse_dependents(dep, map, dependents);
+ }
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct Inner {
+ /// The DENO_DIR that the documents looks for non-file based modules.
+ cache: HttpCache,
+ /// A flag that indicates that stated data is potentially invalid and needs to
+ /// be recalculated before being considered valid.
+ dirty: bool,
+ /// A map where the key is a specifier and the value is a set of specifiers
+ /// that depend on the key.
+ dependents_map: HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>>,
+ /// A map of documents that can either be "open" in the language server, or
+ /// just present on disk.
+ docs: HashMap<ModuleSpecifier, Document>,
+ /// The optional import map that should be used when resolving dependencies.
+ maybe_import_map: Option<ImportMapResolver>,
+ redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
+}
+
+impl Inner {
+ fn new(location: &Path) -> Self {
+ Self {
+ cache: HttpCache::new(location),
+ dirty: true,
+ dependents_map: HashMap::default(),
+ docs: HashMap::default(),
+ maybe_import_map: None,
+ redirects: HashMap::default(),
+ }
+ }
+
+ /// Adds a document by reading the document from the file system.
+ fn add(&mut self, specifier: ModuleSpecifier) -> Option<Document> {
+ let version = self.calculate_version(&specifier)?;
+ let path = self.get_path(&specifier)?;
+ let bytes = fs::read(path).ok()?;
+ let doc = if specifier.scheme() == "file" {
+ let maybe_charset =
+ Some(text_encoding::detect_charset(&bytes).to_string());
+ let content = Arc::new(get_source_from_bytes(bytes, maybe_charset).ok()?);
+ Document::new(
+ specifier.clone(),
+ version,
+ None,
+ content,
+ self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
+ )
+ } else {
+ let cache_filename = self.cache.get_cache_filename(&specifier)?;
+ let metadata = http_cache::Metadata::read(&cache_filename).ok()?;
+ let maybe_content_type = metadata.headers.get("content-type").cloned();
+ let maybe_headers = Some(&metadata.headers);
+ let (_, maybe_charset) = map_content_type(&specifier, maybe_content_type);
+ let content = Arc::new(get_source_from_bytes(bytes, maybe_charset).ok()?);
+ Document::new(
+ specifier.clone(),
+ version,
+ maybe_headers,
+ content,
+ self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
+ )
+ };
+ self.dirty = true;
+ self.docs.insert(specifier, doc)
+ }
+
+ /// 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.
fn calculate_dependents(&mut self) {
- let mut dependents_graph: HashMap<
- ModuleSpecifier,
- HashSet<ModuleSpecifier>,
- > = HashMap::new();
- for (specifier, data) in &self.docs {
- if let Some(dependencies) = &data.dependencies {
- for dependency in dependencies.values() {
- if let Some(analysis::ResolvedDependency::Resolved(dep_specifier)) =
- &dependency.maybe_code
- {
- dependents_graph
- .entry(dep_specifier.clone())
+ let mut dependents_map: HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>> =
+ HashMap::new();
+ for (specifier, doc) in &self.docs {
+ if let Some(Ok(module)) = &doc.maybe_module {
+ for dependency in module.dependencies.values() {
+ if let Some(dep) = dependency.get_code() {
+ dependents_map
+ .entry(dep.clone())
.or_default()
.insert(specifier.clone());
}
- if let Some(analysis::ResolvedDependency::Resolved(dep_specifier)) =
- &dependency.maybe_type
- {
- dependents_graph
- .entry(dep_specifier.clone())
+ if let Some(dep) = dependency.get_type() {
+ dependents_map
+ .entry(dep.clone())
.or_default()
.insert(specifier.clone());
}
}
+ if let Some((_, Some(Ok((dep, _))))) = &module.maybe_types_dependency {
+ dependents_map
+ .entry(dep.clone())
+ .or_default()
+ .insert(specifier.clone());
+ }
}
}
- self.dependents_graph = dependents_graph;
+ self.dependents_map = dependents_map;
}
- pub fn change(
+ fn calculate_version(&self, specifier: &ModuleSpecifier) -> Option<String> {
+ let path = self.get_path(specifier)?;
+ let metadata = fs::metadata(path).ok()?;
+ if let Ok(modified) = metadata.modified() {
+ if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH) {
+ Some(n.as_millis().to_string())
+ } else {
+ Some("1".to_string())
+ }
+ } else {
+ Some("1".to_string())
+ }
+ }
+
+ fn change(
&mut self,
specifier: &ModuleSpecifier,
version: i32,
- content_changes: Vec<lsp::TextDocumentContentChangeEvent>,
+ changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<(), AnyError> {
- if !self.contains_key(specifier) {
- return Err(custom_error(
- "NotFound",
- format!(
- "The specifier (\"{}\") does not exist in the document cache.",
- specifier
- ),
- ));
- }
+ let doc = self.docs.get_mut(specifier).map_or_else(
+ || {
+ Err(custom_error(
+ "NotFound",
+ format!("The specifier \"{}\" was not found.", specifier),
+ ))
+ },
+ Ok,
+ )?;
+ self.dirty = true;
+ doc.change(
+ version,
+ changes,
+ self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
+ )
+ }
- let doc = self.docs.get_mut(specifier).unwrap();
- doc.apply_content_changes(content_changes)?;
- doc.version = Some(version);
+ fn close(&mut self, specifier: &ModuleSpecifier) -> Result<(), AnyError> {
+ let doc = self.docs.get_mut(specifier).map_or_else(
+ || {
+ Err(custom_error(
+ "NotFound",
+ format!("The specifier \"{}\" was not found.", specifier),
+ ))
+ },
+ Ok,
+ )?;
+ doc.close();
+ self.dirty = true;
Ok(())
}
- pub fn close(&mut self, specifier: &ModuleSpecifier) {
- self.docs.remove(specifier);
- self.calculate_dependents();
+ fn contains_import(
+ &mut self,
+ specifier: &str,
+ referrer: &ModuleSpecifier,
+ ) -> bool {
+ let maybe_resolver =
+ self.maybe_import_map.as_ref().map(|im| im.as_resolver());
+ let maybe_specifier = if let Some(resolver) = maybe_resolver {
+ resolver.resolve(specifier, referrer).ok()
+ } else {
+ deno_core::resolve_import(specifier, referrer.as_str()).ok()
+ };
+ if let Some(import_specifier) = maybe_specifier {
+ self.contains_specifier(&import_specifier)
+ } else {
+ false
+ }
}
- pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool {
- self.docs.contains_key(specifier)
+ fn contains_specifier(&mut self, specifier: &ModuleSpecifier) -> bool {
+ let specifier = self
+ .resolve_specifier(specifier)
+ .unwrap_or_else(|| specifier.clone());
+ if !self.is_valid(&specifier) {
+ self.add(specifier.clone());
+ }
+ self.docs.contains_key(&specifier)
}
- pub fn get(&self, specifier: &ModuleSpecifier) -> Option<&DocumentData> {
- self.docs.get(specifier)
+ fn content(&mut self, specifier: &ModuleSpecifier) -> Option<Arc<String>> {
+ self.get(specifier).map(|d| d.content())
}
- pub fn content(&self, specifier: &ModuleSpecifier) -> Option<Arc<String>> {
- self
- .docs
- .get(specifier)
- .map(|d| d.source().text_info().text())
+ fn dependencies(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Vec<(String, deno_graph::Dependency)>> {
+ let doc = self.get(specifier)?;
+ let module = doc.maybe_module.as_ref()?.as_ref().ok()?;
+ Some(
+ module
+ .dependencies
+ .iter()
+ .map(|(s, d)| (s.clone(), d.clone()))
+ .collect(),
+ )
}
- // For a given specifier, get all open documents which directly or indirectly
- // depend upon the specifier.
- pub fn dependents(
- &self,
+ fn dependents(
+ &mut self,
specifier: &ModuleSpecifier,
) -> Vec<ModuleSpecifier> {
+ if self.dirty {
+ self.calculate_dependents();
+ self.dirty = false;
+ }
let mut dependents = HashSet::new();
- self.recurse_dependents(specifier, &mut dependents);
- dependents.into_iter().collect()
+ if let Some(specifier) = self.resolve_specifier(specifier) {
+ recurse_dependents(&specifier, &self.dependents_map, &mut dependents);
+ dependents.into_iter().collect()
+ } else {
+ vec![]
+ }
}
- pub fn dependencies(
- &self,
+ fn get(&mut self, specifier: &ModuleSpecifier) -> Option<&Document> {
+ let specifier = self.resolve_specifier(specifier)?;
+ if !self.is_valid(&specifier) {
+ self.add(specifier.clone());
+ }
+ self.docs.get(&specifier)
+ }
+
+ fn get_maybe_dependency(
+ &mut self,
specifier: &ModuleSpecifier,
- ) -> Option<HashMap<String, analysis::Dependency>> {
- self
- .docs
- .get(specifier)
- .map(|doc| doc.dependencies.clone())
- .flatten()
+ position: &lsp::Position,
+ ) -> Option<(String, deno_graph::Dependency, deno_graph::Range)> {
+ let doc = self.get(specifier)?;
+ let module = doc.maybe_module.as_ref()?.as_ref().ok()?;
+ let position = deno_graph::Position {
+ line: position.line as usize,
+ character: position.character as usize,
+ };
+ module.dependencies.iter().find_map(|(s, dep)| {
+ dep
+ .includes(&position)
+ .map(|r| (s.clone(), dep.clone(), r.clone()))
+ })
}
- pub fn get_navigation_tree(
- &self,
+ fn get_navigation_tree(
+ &mut self,
specifier: &ModuleSpecifier,
- ) -> Option<tsc::NavigationTree> {
+ ) -> Option<Arc<tsc::NavigationTree>> {
self
- .docs
.get(specifier)
- .map(|doc| doc.maybe_navigation_tree.clone())
+ .map(|d| d.maybe_navigation_tree.clone())
.flatten()
}
- /// Determines if the specifier should be processed for diagnostics and other
- /// related language server features.
- pub fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool {
- if specifier.scheme() != "file" {
- // otherwise we look at the media type for the specifier.
- matches!(
- MediaType::from(specifier),
- MediaType::JavaScript
- | MediaType::Jsx
- | MediaType::TypeScript
- | MediaType::Tsx
- | MediaType::Dts
- )
- } else if let Some(doc_data) = self.docs.get(specifier) {
- // if the document is in the document cache, then use the client provided
- // language id to determine if the specifier is diagnosable.
- matches!(
- doc_data.language_id,
- LanguageId::JavaScript
- | LanguageId::Jsx
- | LanguageId::TypeScript
- | LanguageId::Tsx
- )
+ fn get_path(&self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
+ if specifier.scheme() == "file" {
+ specifier.to_file_path().ok()
+ } else {
+ let path = self.cache.get_cache_filename(specifier)?;
+ if path.is_file() {
+ Some(path)
+ } else {
+ None
+ }
+ }
+ }
+
+ fn is_diagnosable(&mut self, specifier: &ModuleSpecifier) -> bool {
+ if let Some(doc) = self.get(specifier) {
+ doc.is_diagnosable()
} else {
false
}
}
- /// Determines if the specifier can be processed for formatting.
- pub fn is_formattable(&self, specifier: &ModuleSpecifier) -> bool {
- self.docs.contains_key(specifier)
+ fn is_formattable(&mut self, specifier: &ModuleSpecifier) -> bool {
+ // currently any document that is open in the language server is formattable
+ self.is_open(specifier)
}
- /// Determines if the position in the document is within a range of a module
- /// specifier, returning the text range if true.
- pub fn is_specifier_position(
- &self,
- specifier: &ModuleSpecifier,
- position: &lsp::Position,
- ) -> Option<analysis::DependencyRange> {
- let document = self.docs.get(specifier)?;
- document.is_specifier_position(position)
+ fn is_open(&mut self, specifier: &ModuleSpecifier) -> bool {
+ let specifier = self
+ .resolve_specifier(specifier)
+ .unwrap_or_else(|| specifier.clone());
+ // this does not use `self.get` since that lazily adds documents, and we
+ // only care about documents already in the cache.
+ if let Some(doc) = self.docs.get(&specifier) {
+ doc.is_open()
+ } else {
+ false
+ }
}
- pub fn len(&self) -> usize {
- self.docs.len()
+ fn is_valid(&mut self, specifier: &ModuleSpecifier) -> bool {
+ if self.is_open(specifier) {
+ true
+ } else if let Some(specifier) = self.resolve_specifier(specifier) {
+ self.docs.get(&specifier).map(|d| d.version.clone())
+ == self.calculate_version(&specifier)
+ } else {
+ // even though it isn't valid, it just can't exist, so we will say it is
+ // valid
+ true
+ }
}
- pub fn line_index(&self, specifier: &ModuleSpecifier) -> Option<LineIndex> {
+ fn line_index(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Arc<LineIndex>> {
+ let specifier = self.resolve_specifier(specifier)?;
+ self.docs.get(&specifier).map(|doc| doc.line_index.clone())
+ }
+
+ fn lsp_version(&self, specifier: &ModuleSpecifier) -> Option<i32> {
self
.docs
.get(specifier)
- .map(|d| d.source().line_index().clone())
+ .map(|doc| doc.maybe_lsp_version)
+ .flatten()
}
- pub fn open(
+ fn maybe_warning(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
+ self
+ .get(specifier)
+ .map(|d| d.maybe_warning.clone())
+ .flatten()
+ }
+
+ fn open(
&mut self,
specifier: ModuleSpecifier,
version: i32,
language_id: LanguageId,
- source: Arc<String>,
+ content: Arc<String>,
) {
- self.docs.insert(
+ let document_data = Document::open(
specifier.clone(),
- DocumentData::new(specifier, version, language_id, source),
+ version,
+ language_id,
+ content,
+ self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
);
+ self.docs.insert(specifier, document_data);
+ self.dirty = true;
}
- pub fn open_specifiers(&self) -> Vec<&ModuleSpecifier> {
+ fn parsed_source(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Result<deno_ast::ParsedSource, deno_graph::ModuleGraphError>> {
self
- .docs
- .iter()
- .filter_map(|(key, data)| {
- if data.version.is_some() {
- Some(key)
+ .get(specifier)
+ .map(|doc| {
+ doc.maybe_module.as_ref().map(|r| {
+ r.as_ref()
+ .map(|m| m.parsed_source.clone())
+ .map_err(|err| err.clone())
+ })
+ })
+ .flatten()
+ }
+
+ fn resolve(
+ &mut self,
+ specifiers: Vec<String>,
+ referrer: &ModuleSpecifier,
+ ) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> {
+ let doc = self.get(referrer)?;
+ let mut results = Vec::new();
+ if let Some(Ok(module)) = &doc.maybe_module {
+ let dependencies = module.dependencies.clone();
+ for specifier in specifiers {
+ if specifier.starts_with("asset:") {
+ if let Ok(specifier) = ModuleSpecifier::parse(&specifier) {
+ let media_type = MediaType::from(&specifier);
+ results.push(Some((specifier, media_type)));
+ } else {
+ results.push(None);
+ }
+ } else if let Some(dep) = dependencies.get(&specifier) {
+ if let Some(Ok((specifier, _))) = &dep.maybe_type {
+ results.push(self.resolve_dependency(specifier));
+ } else if let Some(Ok((specifier, _))) = &dep.maybe_code {
+ results.push(self.resolve_dependency(specifier));
+ } else {
+ results.push(None);
+ }
} else {
- None
+ results.push(None);
}
+ }
+ }
+ Some(results)
+ }
+
+ fn resolve_dependency(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<(ModuleSpecifier, MediaType)> {
+ let doc = self.get(specifier)?;
+ let maybe_module =
+ doc.maybe_module.as_ref().map(|r| r.as_ref().ok()).flatten();
+ let maybe_types_dependency = maybe_module
+ .map(|m| {
+ m.maybe_types_dependency
+ .as_ref()
+ .map(|(_, o)| o.as_ref().map(|r| r.as_ref().ok()).flatten())
+ .flatten()
})
- .collect()
+ .flatten()
+ .cloned();
+ if let Some((specifier, _)) = maybe_types_dependency {
+ self.resolve_dependency(&specifier)
+ } else {
+ let media_type = doc.media_type();
+ Some((specifier.clone(), media_type))
+ }
}
- fn recurse_dependents(
+ fn resolve_remote_specifier(
&self,
specifier: &ModuleSpecifier,
- dependents: &mut HashSet<ModuleSpecifier>,
- ) {
- if let Some(deps) = self.dependents_graph.get(specifier) {
- for dep in deps {
- if !dependents.contains(dep) {
- dependents.insert(dep.clone());
- self.recurse_dependents(dep, dependents);
- }
+ redirect_limit: usize,
+ ) -> Option<ModuleSpecifier> {
+ let cache_filename = self.cache.get_cache_filename(specifier)?;
+ if redirect_limit > 0 && cache_filename.is_file() {
+ let headers = http_cache::Metadata::read(&cache_filename)
+ .ok()
+ .map(|m| m.headers)?;
+ if let Some(location) = headers.get("location") {
+ let redirect =
+ deno_core::resolve_import(location, specifier.as_str()).ok()?;
+ self.resolve_remote_specifier(&redirect, redirect_limit - 1)
+ } else {
+ Some(specifier.clone())
}
+ } else {
+ None
}
}
- pub fn set_dependencies(
+ fn resolve_specifier(
&mut self,
specifier: &ModuleSpecifier,
- maybe_dependencies: Option<HashMap<String, analysis::Dependency>>,
- maybe_dependency_ranges: Option<analysis::DependencyRanges>,
- ) -> Result<(), AnyError> {
- if let Some(doc) = self.docs.get_mut(specifier) {
- doc.dependencies = maybe_dependencies;
- doc.dependency_ranges = maybe_dependency_ranges;
- self.calculate_dependents();
- Ok(())
+ ) -> Option<ModuleSpecifier> {
+ let scheme = specifier.scheme();
+ if !SUPPORTED_SCHEMES.contains(&scheme) {
+ return None;
+ }
+
+ if scheme == "data" || scheme == "blob" || scheme == "file" {
+ Some(specifier.clone())
+ } else if let Some(specifier) = self.redirects.get(specifier) {
+ Some(specifier.clone())
} else {
- Err(custom_error(
- "NotFound",
- format!(
- "The specifier (\"{}\") does not exist in the document cache.",
- specifier
- ),
- ))
+ let redirect = self.resolve_remote_specifier(specifier, 10)?;
+ self.redirects.insert(specifier.clone(), redirect.clone());
+ Some(redirect)
}
}
- pub fn set_navigation_tree(
+ fn set_import_map(
+ &mut self,
+ maybe_import_map: Option<Arc<import_map::ImportMap>>,
+ ) {
+ // TODO update resolved dependencies?
+ self.maybe_import_map = maybe_import_map.map(ImportMapResolver::new);
+ self.dirty = true;
+ }
+
+ fn set_location(&mut self, location: PathBuf) {
+ // TODO update resolved dependencies?
+ self.cache = HttpCache::new(&location);
+ self.dirty = true;
+ }
+
+ fn set_navigation_tree(
&mut self,
specifier: &ModuleSpecifier,
- navigation_tree: tsc::NavigationTree,
+ navigation_tree: Arc<tsc::NavigationTree>,
) -> Result<(), AnyError> {
- if let Some(doc) = self.docs.get_mut(specifier) {
- doc.maybe_navigation_tree = Some(navigation_tree);
- Ok(())
- } else {
- Err(custom_error(
- "NotFound",
- format!(
- "The specifier (\"{}\") does not exist in the document cache.",
- specifier
- ),
- ))
- }
+ let doc = self.docs.get_mut(specifier).ok_or_else(|| {
+ custom_error("NotFound", format!("Specifier not found {}", specifier))
+ })?;
+ doc.maybe_navigation_tree = Some(navigation_tree);
+ Ok(())
+ }
+
+ fn specifiers(
+ &self,
+ open_only: bool,
+ diagnosable_only: bool,
+ ) -> Vec<ModuleSpecifier> {
+ self
+ .docs
+ .iter()
+ .filter_map(|(specifier, doc)| {
+ let open = open_only && doc.is_open();
+ let diagnosable = diagnosable_only && doc.is_diagnosable();
+ if (!open_only || open) && (!diagnosable_only || diagnosable) {
+ Some(specifier.clone())
+ } else {
+ None
+ }
+ })
+ .collect()
+ }
+
+ fn text_info(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<SourceTextInfo> {
+ self.get(specifier).map(|d| d.source.clone())
+ }
+
+ fn version(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
+ self.get(specifier).map(|d| {
+ d.maybe_lsp_version
+ .map_or_else(|| d.version.clone(), |v| v.to_string())
+ })
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub(crate) struct Documents(Arc<Mutex<Inner>>);
+
+impl Documents {
+ pub fn new(location: &Path) -> Self {
+ Self(Arc::new(Mutex::new(Inner::new(location))))
+ }
+
+ /// Apply language server content changes to an open document.
+ pub fn change(
+ &self,
+ specifier: &ModuleSpecifier,
+ version: i32,
+ changes: Vec<lsp::TextDocumentContentChangeEvent>,
+ ) -> Result<(), AnyError> {
+ self.0.lock().change(specifier, version, changes)
+ }
+
+ /// 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.
+ pub fn close(&self, specifier: &ModuleSpecifier) -> Result<(), AnyError> {
+ self.0.lock().close(specifier)
+ }
+
+ /// Return `true` if the provided specifier can be resolved to a document,
+ /// otherwise `false`.
+ pub fn contains_import(
+ &self,
+ specifier: &str,
+ referrer: &ModuleSpecifier,
+ ) -> bool {
+ self.0.lock().contains_import(specifier, referrer)
+ }
+
+ /// Return `true` if the specifier can be resolved to a document.
+ pub fn contains_specifier(&self, specifier: &ModuleSpecifier) -> bool {
+ self.0.lock().contains_specifier(specifier)
+ }
+
+ /// If the specifier can be resolved to a document, return its current
+ /// content, otherwise none.
+ pub fn content(&self, specifier: &ModuleSpecifier) -> Option<Arc<String>> {
+ self.0.lock().content(specifier)
+ }
+
+ /// Return an optional vector of dependencies for a given specifier.
+ pub fn dependencies(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Vec<(String, deno_graph::Dependency)>> {
+ self.0.lock().dependencies(specifier)
+ }
+
+ /// Return an array of specifiers, if any, that are dependent upon the
+ /// supplied specifier. This is used to determine invalidation of diagnostics
+ /// when a module has been changed.
+ pub fn dependents(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Vec<ModuleSpecifier> {
+ self.0.lock().dependents(specifier)
+ }
+
+ /// 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,
+ specifier: &ModuleSpecifier,
+ position: &lsp::Position,
+ ) -> Option<(String, deno_graph::Dependency, deno_graph::Range)> {
+ self.0.lock().get_maybe_dependency(specifier, position)
+ }
+
+ /// Get a reference to the navigation tree stored for a given specifier, if
+ /// any.
+ pub fn get_navigation_tree(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Arc<tsc::NavigationTree>> {
+ self.0.lock().get_navigation_tree(specifier)
+ }
+
+ /// Indicates that a specifier is able to be diagnosed by the language server
+ pub fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool {
+ self.0.lock().is_diagnosable(specifier)
+ }
+
+ /// Indicates that a specifier is formattable.
+ pub fn is_formattable(&self, specifier: &ModuleSpecifier) -> bool {
+ self.0.lock().is_formattable(specifier)
+ }
+
+ /// Return a reference to the line index for a given specifiers, if any.
+ pub fn line_index(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Arc<LineIndex>> {
+ self.0.lock().line_index(specifier)
+ }
+
+ /// Return the current language server client version for a given specifier,
+ /// if any.
+ pub fn lsp_version(&self, specifier: &ModuleSpecifier) -> Option<i32> {
+ self.0.lock().lsp_version(specifier)
+ }
+
+ /// Return a warning header for a given specifier, if present.
+ pub fn maybe_warning(&self, specifier: &ModuleSpecifier) -> Option<String> {
+ self.0.lock().maybe_warning(specifier)
+ }
+
+ /// "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(
+ &self,
+ specifier: ModuleSpecifier,
+ version: i32,
+ language_id: LanguageId,
+ content: Arc<String>,
+ ) {
+ self.0.lock().open(specifier, version, language_id, content)
+ }
+
+ /// Return the parsed source or the module graph error for a given specifier.
+ pub fn parsed_source(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Result<deno_ast::ParsedSource, deno_graph::ModuleGraphError>> {
+ self.0.lock().parsed_source(specifier)
+ }
+
+ /// 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,
+ specifiers: Vec<String>,
+ referrer: &ModuleSpecifier,
+ ) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> {
+ self.0.lock().resolve(specifiers, referrer)
+ }
+
+ /// Set the optional import map for the document cache.
+ pub fn set_import_map(
+ &self,
+ maybe_import_map: Option<Arc<import_map::ImportMap>>,
+ ) {
+ self.0.lock().set_import_map(maybe_import_map);
}
- pub fn specifiers(&self) -> Vec<ModuleSpecifier> {
- self.docs.keys().cloned().collect()
+ /// Update the location of the on disk cache for the document store.
+ pub fn set_location(&self, location: PathBuf) {
+ self.0.lock().set_location(location)
}
- pub fn version(&self, specifier: &ModuleSpecifier) -> Option<i32> {
- self.docs.get(specifier).and_then(|doc| doc.version)
+ /// Set a navigation tree that is associated with the provided specifier.
+ pub fn set_navigation_tree(
+ &self,
+ specifier: &ModuleSpecifier,
+ navigation_tree: Arc<tsc::NavigationTree>,
+ ) -> Result<(), AnyError> {
+ self
+ .0
+ .lock()
+ .set_navigation_tree(specifier, navigation_tree)
+ }
+
+ /// Return a vector of specifiers that are contained in the document store,
+ /// where `open_only` flag would provide only those documents currently open
+ /// in the editor and `diagnosable_only` would provide only those documents
+ /// that the language server can provide diagnostics for.
+ pub fn specifiers(
+ &self,
+ open_only: bool,
+ diagnosable_only: bool,
+ ) -> Vec<ModuleSpecifier> {
+ self.0.lock().specifiers(open_only, diagnosable_only)
+ }
+
+ /// Return the current text info for a given specifier.
+ pub fn text_info(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<SourceTextInfo> {
+ self.0.lock().text_info(specifier)
+ }
+
+ /// Return the version of a document in the document cache.
+ pub fn version(&self, specifier: &ModuleSpecifier) -> Option<String> {
+ self.0.lock().version(specifier)
}
}
#[cfg(test)]
mod tests {
use super::*;
- use deno_core::resolve_url;
- use lspower::lsp;
+ use tempfile::TempDir;
- #[test]
- fn test_language_id() {
- assert_eq!(
- "javascript".parse::<LanguageId>().unwrap(),
- LanguageId::JavaScript
- );
- assert_eq!(
- "javascriptreact".parse::<LanguageId>().unwrap(),
- LanguageId::Jsx
- );
- assert_eq!("jsx".parse::<LanguageId>().unwrap(), LanguageId::Jsx);
- assert_eq!(
- "typescript".parse::<LanguageId>().unwrap(),
- LanguageId::TypeScript
- );
- assert_eq!(
- "typescriptreact".parse::<LanguageId>().unwrap(),
- LanguageId::Tsx
- );
- assert_eq!("tsx".parse::<LanguageId>().unwrap(), LanguageId::Tsx);
- assert_eq!("json".parse::<LanguageId>().unwrap(), LanguageId::Json);
- assert_eq!("jsonc".parse::<LanguageId>().unwrap(), LanguageId::JsonC);
- assert_eq!(
- "markdown".parse::<LanguageId>().unwrap(),
- LanguageId::Markdown
- );
- assert_eq!("rust".parse::<LanguageId>().unwrap(), LanguageId::Unknown);
+ fn setup() -> (Documents, PathBuf) {
+ let temp_dir = TempDir::new().unwrap();
+ let location = temp_dir.path().join("deps");
+ let documents = Documents::new(&location);
+ (documents, location)
}
#[test]
- fn test_document_cache_contains() {
- let mut document_cache = DocumentCache::default();
- let specifier = resolve_url("file:///a/b.ts").unwrap();
- let missing_specifier = resolve_url("file:///a/c.ts").unwrap();
- document_cache.open(
- specifier.clone(),
- 1,
- LanguageId::TypeScript,
- Arc::new("console.log(\"Hello Deno\");\n".to_string()),
+ fn test_documents_open() {
+ let (documents, _) = setup();
+ let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
+ let content = Arc::new(
+ r#"import * as b from "./b.ts";
+console.log(b);
+"#
+ .to_string(),
);
- assert!(document_cache.contains_key(&specifier));
- assert!(!document_cache.contains_key(&missing_specifier));
- }
-
- #[test]
- fn test_document_cache_change() {
- let mut document_cache = DocumentCache::default();
- let specifier = resolve_url("file:///a/b.ts").unwrap();
- document_cache.open(
+ documents.open(
specifier.clone(),
1,
- LanguageId::TypeScript,
- Arc::new("console.log(\"Hello deno\");\n".to_string()),
+ "javascript".parse().unwrap(),
+ content,
);
- document_cache
- .change(
- &specifier,
- 2,
- vec![lsp::TextDocumentContentChangeEvent {
- range: Some(lsp::Range {
- start: lsp::Position {
- line: 0,
- character: 19,
- },
- end: lsp::Position {
- line: 0,
- character: 20,
- },
- }),
- range_length: Some(1),
- text: "D".to_string(),
- }],
- )
- .expect("failed to make changes");
- let actual = document_cache
- .content(&specifier)
- .expect("failed to get content")
- .to_string();
- assert_eq!(actual, "console.log(\"Hello Deno\");\n");
+ assert!(documents.is_formattable(&specifier));
+ assert!(documents.is_diagnosable(&specifier));
+ assert!(documents.line_index(&specifier).is_some());
}
#[test]
- fn test_document_cache_change_utf16() {
- let mut document_cache = DocumentCache::default();
- let specifier = resolve_url("file:///a/b.ts").unwrap();
- document_cache.open(
+ fn test_documents_change() {
+ let (documents, _) = setup();
+ let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
+ let content = Arc::new(
+ r#"import * as b from "./b.ts";
+console.log(b);
+"#
+ .to_string(),
+ );
+ documents.open(
specifier.clone(),
1,
- LanguageId::TypeScript,
- Arc::new("console.log(\"Hello 🦕\");\n".to_string()),
+ "javascript".parse().unwrap(),
+ content,
);
- document_cache
+ documents
.change(
&specifier,
2,
vec![lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range {
start: lsp::Position {
- line: 0,
- character: 19,
+ line: 1,
+ character: 13,
},
end: lsp::Position {
- line: 0,
- character: 21,
+ line: 1,
+ character: 13,
},
}),
- range_length: Some(2),
- text: "Deno".to_string(),
+ range_length: None,
+ text: r#", "hello deno""#.to_string(),
}],
)
- .expect("failed to make changes");
- let actual = document_cache
- .content(&specifier)
- .expect("failed to get content")
- .to_string();
- assert_eq!(actual, "console.log(\"Hello Deno\");\n");
- }
-
- #[test]
- fn test_is_diagnosable() {
- let mut document_cache = DocumentCache::default();
- let specifier = resolve_url("file:///a/file.ts").unwrap();
- assert!(!document_cache.is_diagnosable(&specifier));
- document_cache.open(
- specifier.clone(),
- 1,
- "typescript".parse().unwrap(),
- Arc::new("console.log(\"hello world\");\n".to_string()),
- );
- assert!(document_cache.is_diagnosable(&specifier));
- let specifier = resolve_url("file:///a/file.rs").unwrap();
- document_cache.open(
- specifier.clone(),
- 1,
- "rust".parse().unwrap(),
- Arc::new("pub mod a;".to_string()),
+ .unwrap();
+ assert_eq!(
+ documents.content(&specifier).unwrap().as_str(),
+ r#"import * as b from "./b.ts";
+console.log(b, "hello deno");
+"#
);
- assert!(!document_cache.is_diagnosable(&specifier));
- let specifier =
- resolve_url("asset:///lib.es2015.symbol.wellknown.d.ts").unwrap();
- assert!(document_cache.is_diagnosable(&specifier));
- let specifier = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
- assert!(document_cache.is_diagnosable(&specifier));
- let specifier = resolve_url("data:application/json;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
- assert!(!document_cache.is_diagnosable(&specifier));
}
}
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 13fcafec1..b56093c7b 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+use deno_ast::MediaType;
use deno_core::error::anyhow;
use deno_core::error::AnyError;
use deno_core::resolve_url;
@@ -23,12 +24,10 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use tokio::fs;
-use super::analysis;
use super::analysis::fix_ts_import_changes;
use super::analysis::ts_changes_to_edit;
use super::analysis::CodeActionCollection;
use super::analysis::CodeActionData;
-use super::analysis::ResolvedDependency;
use super::cache::CacheServer;
use super::capabilities;
use super::code_lens;
@@ -38,14 +37,15 @@ use super::config::ConfigSnapshot;
use super::config::SETTINGS_SECTION;
use super::diagnostics;
use super::diagnostics::DiagnosticSource;
-use super::documents::DocumentCache;
+use super::documents::to_hover_text;
+use super::documents::to_lsp_range;
+use super::documents::Documents;
use super::documents::LanguageId;
use super::lsp_custom;
use super::parent_process_checker;
use super::performance::Performance;
use super::refactor;
use super::registries;
-use super::sources::Sources;
use super::text;
use super::text::LineIndex;
use super::tsc;
@@ -65,22 +65,21 @@ use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source;
pub const REGISTRIES_PATH: &str = "registries";
-const SOURCES_PATH: &str = "deps";
+const CACHE_PATH: &str = "deps";
#[derive(Debug, Clone)]
pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
#[derive(Debug, Clone, Default)]
-pub struct StateSnapshot {
+pub(crate) struct StateSnapshot {
pub assets: Assets,
pub config: ConfigSnapshot,
- pub documents: DocumentCache,
+ pub documents: Documents,
pub maybe_lint_config: Option<LintConfig>,
pub maybe_fmt_config: Option<FmtConfig>,
pub maybe_config_uri: Option<ModuleSpecifier>,
pub module_registries: registries::ModuleRegistry,
pub performance: Performance,
- pub sources: Sources,
pub url_map: urls::LspUrlMap,
}
@@ -94,8 +93,9 @@ pub(crate) struct Inner {
/// Configuration information.
pub(crate) config: Config,
diagnostics_server: diagnostics::DiagnosticsServer,
- /// The "in-memory" documents in the editor which can be updated and changed.
- documents: DocumentCache,
+ /// The collection of documents that the server is currently handling, either
+ /// on disk or "open" within the client.
+ documents: Documents,
/// Handles module registries, which allow discovery of modules
module_registries: registries::ModuleRegistry,
/// The path to the module registries cache
@@ -121,8 +121,6 @@ pub(crate) struct Inner {
maybe_import_map_uri: Option<Url>,
/// A collection of measurements which instrument that performance of the LSP.
performance: Performance,
- /// Cached sources that are read-only.
- sources: Sources,
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
ts_fixable_diagnostics: Vec<String>,
/// An abstraction that handles interactions with TypeScript.
@@ -145,19 +143,17 @@ impl Inner {
let module_registries_location = dir.root.join(REGISTRIES_PATH);
let module_registries =
registries::ModuleRegistry::new(&module_registries_location);
- let sources_location = dir.root.join(SOURCES_PATH);
- let sources = Sources::new(&sources_location);
+ let location = dir.root.join(CACHE_PATH);
+ let documents = Documents::new(&location);
let ts_server = Arc::new(TsServer::new());
- let performance = Performance::default();
- let diagnostics_server = diagnostics::DiagnosticsServer::new();
let config = Config::new(client.clone());
Self {
assets: Default::default(),
client,
config,
- diagnostics_server,
- documents: Default::default(),
+ diagnostics_server: Default::default(),
+ documents,
maybe_cache_path: None,
maybe_lint_config: None,
maybe_fmt_config: None,
@@ -168,69 +164,20 @@ impl Inner {
maybe_import_map_uri: None,
module_registries,
module_registries_location,
- performance,
- sources,
+ performance: Default::default(),
ts_fixable_diagnostics: Default::default(),
ts_server,
url_map: Default::default(),
}
}
- /// Analyzes dependencies of a document that has been opened in the editor and
- /// sets the dependencies property on the document.
- fn analyze_dependencies(&mut self, specifier: &ModuleSpecifier) {
- if let Some(Ok(parsed_module)) = self
- .documents
- .get(specifier)
- .map(|d| d.source().module())
- .flatten()
- {
- let (mut deps, _) = analysis::analyze_dependencies(
- specifier,
- parsed_module.media_type(),
- parsed_module,
- &self.maybe_import_map,
- );
- for (_, dep) in deps.iter_mut() {
- if dep.maybe_type.is_none() {
- if let Some(ResolvedDependency::Resolved(resolved)) = &dep.maybe_code
- {
- dep.maybe_type = self.sources.get_maybe_types(resolved);
- }
- }
- }
- let dep_ranges = analysis::analyze_dependency_ranges(parsed_module).ok();
- if let Err(err) =
- self
- .documents
- .set_dependencies(specifier, Some(deps), dep_ranges)
- {
- error!("{}", err);
- }
- }
- }
-
- /// Analyzes all dependencies for all documents that have been opened in the
- /// editor and sets the dependencies property on the documents.
- fn analyze_dependencies_all(&mut self) {
- let specifiers = self
- .documents
- .docs
- .keys()
- .map(ToOwned::to_owned)
- .collect::<Vec<_>>();
- for specifier in specifiers {
- self.analyze_dependencies(&specifier);
- }
- }
-
/// Searches assets, open documents and external sources for a line_index,
/// which might be performed asynchronously, hydrating in memory caches for
/// subsequent requests.
pub(crate) async fn get_line_index(
&mut self,
specifier: ModuleSpecifier,
- ) -> Result<LineIndex, AnyError> {
+ ) -> Result<Arc<LineIndex>, AnyError> {
let mark = self
.performance
.mark("get_line_index", Some(json!({ "specifier": specifier })));
@@ -242,8 +189,6 @@ impl Inner {
}
} else if let Some(line_index) = self.documents.line_index(&specifier) {
Ok(line_index)
- } else if let Some(line_index) = self.sources.get_line_index(&specifier) {
- Ok(line_index)
} else {
Err(anyhow!("Unable to find line index for: {}", specifier))
};
@@ -256,7 +201,7 @@ impl Inner {
pub fn get_line_index_sync(
&self,
specifier: &ModuleSpecifier,
- ) -> Option<LineIndex> {
+ ) -> Option<Arc<LineIndex>> {
let mark = self.performance.mark(
"get_line_index_sync",
Some(json!({ "specifier": specifier })),
@@ -268,12 +213,7 @@ impl Inner {
None
}
} else {
- let documents = &self.documents;
- if documents.contains_key(specifier) {
- documents.line_index(specifier)
- } else {
- self.sources.get_line_index(specifier)
- }
+ self.documents.line_index(specifier)
};
self.performance.measure(mark);
maybe_line_index
@@ -294,31 +234,23 @@ impl Inner {
.assets
.get(specifier)
.map(|o| o.clone().map(|a| a.text))?
- } else if self.documents.contains_key(specifier) {
- self.documents.content(specifier)
} else {
- self.sources.get_source(specifier)
+ self.documents.content(specifier)
}
}
pub(crate) async fn get_navigation_tree(
&mut self,
specifier: &ModuleSpecifier,
- ) -> Result<tsc::NavigationTree, AnyError> {
+ ) -> Result<Arc<tsc::NavigationTree>, AnyError> {
let mark = self.performance.mark(
"get_navigation_tree",
Some(json!({ "specifier": specifier })),
);
let maybe_navigation_tree = if specifier.scheme() == "asset" {
- self
- .assets
- .get(specifier)
- .map(|o| o.clone().map(|a| a.maybe_navigation_tree).flatten())
- .flatten()
- } else if self.documents.contains_key(specifier) {
- self.documents.get_navigation_tree(specifier)
+ self.assets.get_navigation_tree(specifier)
} else {
- self.sources.get_navigation_tree(specifier)
+ self.documents.get_navigation_tree(specifier)
};
let navigation_tree = if let Some(navigation_tree) = maybe_navigation_tree {
navigation_tree
@@ -330,17 +262,14 @@ impl Inner {
tsc::RequestMethod::GetNavigationTree(specifier.clone()),
)
.await?;
+ let navigation_tree = Arc::new(navigation_tree);
if specifier.scheme() == "asset" {
self
.assets
.set_navigation_tree(specifier, navigation_tree.clone())?;
- } else if self.documents.contains_key(specifier) {
- self
- .documents
- .set_navigation_tree(specifier, navigation_tree.clone())?;
} else {
self
- .sources
+ .documents
.set_navigation_tree(specifier, navigation_tree.clone())?;
}
navigation_tree
@@ -394,6 +323,21 @@ impl Inner {
Ok(None)
}
+ fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool {
+ if specifier.scheme() == "asset" {
+ matches!(
+ MediaType::from(specifier),
+ MediaType::JavaScript
+ | MediaType::Jsx
+ | MediaType::TypeScript
+ | MediaType::Tsx
+ | MediaType::Dts
+ )
+ } else {
+ self.documents.is_diagnosable(specifier)
+ }
+ }
+
fn merge_user_tsconfig(
&mut self,
tsconfig: &mut TsConfig,
@@ -424,7 +368,6 @@ impl Inner {
maybe_config_uri: self.maybe_config_uri.clone(),
module_registries: self.module_registries.clone(),
performance: self.performance.clone(),
- sources: self.sources.clone(),
url_map: self.url_map.clone(),
})
}
@@ -479,8 +422,7 @@ impl Inner {
self.module_registries =
registries::ModuleRegistry::new(&module_registries_location);
self.module_registries_location = module_registries_location;
- let sources_location = dir.root.join(SOURCES_PATH);
- self.sources = Sources::new(&sources_location);
+ self.documents.set_location(dir.root.join(CACHE_PATH));
self.maybe_cache_path = maybe_cache_path;
}
Ok(())
@@ -542,9 +484,9 @@ impl Inner {
ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
self.maybe_import_map_uri = Some(import_map_url);
self.maybe_import_map = Some(import_map.clone());
- self.sources.set_import_map(Some(import_map));
+ self.documents.set_import_map(Some(Arc::new(import_map)));
} else {
- self.sources.set_import_map(None);
+ self.documents.set_import_map(None);
self.maybe_import_map = None;
}
self.performance.measure(mark);
@@ -651,9 +593,9 @@ impl Inner {
pub(crate) fn document_version(
&self,
- specifier: ModuleSpecifier,
+ specifier: &ModuleSpecifier,
) -> Option<i32> {
- self.documents.version(&specifier)
+ self.documents.lsp_version(specifier)
}
async fn get_asset(
@@ -833,15 +775,15 @@ impl Inner {
params.text_document.language_id, params.text_document.uri
);
}
+ let content = Arc::new(params.text_document.text);
self.documents.open(
specifier.clone(),
params.text_document.version,
- language_id,
- Arc::new(params.text_document.text),
+ params.text_document.language_id.parse().unwrap(),
+ content,
);
- if self.documents.is_diagnosable(&specifier) {
- self.analyze_dependencies(&specifier);
+ if self.is_diagnosable(&specifier) {
self
.diagnostics_server
.invalidate(self.documents.dependents(&specifier))
@@ -850,6 +792,7 @@ impl Inner {
error!("{}", err);
}
}
+
self.performance.measure(mark);
}
@@ -862,8 +805,7 @@ impl Inner {
params.content_changes,
) {
Ok(()) => {
- if self.documents.is_diagnosable(&specifier) {
- self.analyze_dependencies(&specifier);
+ if self.is_diagnosable(&specifier) {
self
.diagnostics_server
.invalidate(self.documents.dependents(&specifier))
@@ -887,15 +829,14 @@ impl Inner {
return;
}
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- let is_diagnosable = self.documents.is_diagnosable(&specifier);
- if is_diagnosable {
+ if let Err(err) = self.documents.close(&specifier) {
+ error!("{}", err);
+ }
+ if self.is_diagnosable(&specifier) {
let mut specifiers = self.documents.dependents(&specifier);
specifiers.push(specifier.clone());
self.diagnostics_server.invalidate(specifiers).await;
- }
- self.documents.close(&specifier);
- if is_diagnosable {
if let Err(err) = self.diagnostics_server.update() {
error!("{}", err);
}
@@ -998,7 +939,6 @@ impl Inner {
}
}
if touched {
- self.analyze_dependencies_all();
self.diagnostics_server.invalidate_all().await;
if let Err(err) = self.diagnostics_server.update() {
error!("Cannot update diagnostics: {}", err);
@@ -1012,7 +952,7 @@ impl Inner {
params: DocumentSymbolParams,
) -> LspResult<Option<DocumentSymbolResponse>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
@@ -1020,15 +960,15 @@ impl Inner {
let mark = self.performance.mark("document_symbol", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetNavigationTree(specifier);
let navigation_tree: tsc::NavigationTree = self
@@ -1043,7 +983,8 @@ impl Inner {
let response = if let Some(child_items) = navigation_tree.child_items {
let mut document_symbols = Vec::<DocumentSymbol>::new();
for item in child_items {
- item.collect_document_symbols(&line_index, &mut document_symbols);
+ item
+ .collect_document_symbols(line_index.clone(), &mut document_symbols);
}
Some(DocumentSymbolResponse::Nested(document_symbols))
} else {
@@ -1062,11 +1003,6 @@ impl Inner {
return Ok(None);
}
let mark = self.performance.mark("formatting", Some(&params));
- let document_data = self.documents.get(&specifier).ok_or_else(|| {
- LspError::invalid_params(
- "The specified file could not be found in memory.",
- )
- })?;
let file_path =
if let Ok(file_path) = params.text_document.uri.to_file_path() {
file_path
@@ -1080,28 +1016,35 @@ impl Inner {
Default::default()
};
- let source = document_data.source().clone();
+ let content = self.documents.content(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(
+ "The specified file could not be found in memory.",
+ ))
+ },
+ Ok,
+ )?;
+ let line_index = self.documents.line_index(&specifier).unwrap();
+ let maybe_parsed_source = self.documents.parsed_source(&specifier);
+
let text_edits = tokio::task::spawn_blocking(move || {
- let format_result = match source.module() {
- Some(Ok(parsed_module)) => {
- format_parsed_source(parsed_module, fmt_options)
+ let format_result = match maybe_parsed_source {
+ Some(Ok(parsed_source)) => {
+ format_parsed_source(&parsed_source, fmt_options)
}
Some(Err(err)) => Err(err.to_string()),
None => {
// it's not a js/ts file, so attempt to format its contents
- format_file(&file_path, source.text_info().text_str(), fmt_options)
+ format_file(&file_path, content.as_str(), fmt_options)
}
};
match format_result {
- Ok(new_text) => {
- let line_index = source.line_index();
- Some(text::get_edits(
- source.text_info().text_str(),
- &new_text,
- line_index,
- ))
- }
+ Ok(new_text) => Some(text::get_edits(
+ content.as_str(),
+ &new_text,
+ line_index.as_ref(),
+ )),
Err(err) => {
// TODO(lucacasonato): handle error properly
warn!("Format error: {}", err);
@@ -1129,72 +1072,51 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("hover", Some(&params));
- let hover = if let Some(dependency_range) =
- self.documents.is_specifier_position(
+ let hover = if let Some((_, dep, range)) =
+ self.documents.get_maybe_dependency(
&specifier,
&params.text_document_position_params.position,
) {
- if let Some(dependencies) = &self.documents.dependencies(&specifier) {
- if let Some(dep) = dependencies.get(&dependency_range.specifier) {
- let value = match (&dep.maybe_code, &dep.maybe_type) {
- (Some(code_dep), Some(type_dep)) => {
- format!(
- "**Resolved Dependency**\n\n**Code**: {}\n\n**Types**: {}\n",
- code_dep.as_hover_text(),
- type_dep.as_hover_text()
- )
- }
- (Some(code_dep), None) => {
- format!(
- "**Resolved Dependency**\n\n**Code**: {}\n",
- code_dep.as_hover_text()
- )
- }
- (None, Some(type_dep)) => {
- format!(
- "**Resolved Dependency**\n\n**Types**: {}\n",
- type_dep.as_hover_text()
- )
- }
- (None, None) => {
- error!(
- "Unexpected state hovering on dependency. Dependency \"{}\" in \"{}\" not found.",
- dependency_range.specifier,
- specifier
- );
- "".to_string()
- }
- };
- Some(Hover {
- contents: HoverContents::Markup(MarkupContent {
- kind: MarkupKind::Markdown,
- value,
- }),
- range: Some(dependency_range.range),
- })
- } else {
- None
- }
- } else {
- None
- }
+ let value = match (&dep.maybe_code, &dep.maybe_type) {
+ (Some(code_dep), Some(type_dep)) => format!(
+ "**Resolved Dependency**\n\n**Code**: {}\n\n**Types**: {}\n",
+ to_hover_text(code_dep),
+ to_hover_text(type_dep)
+ ),
+ (Some(code_dep), None) => format!(
+ "**Resolved Dependency**\n\n**Code**: {}\n",
+ to_hover_text(code_dep)
+ ),
+ (None, Some(type_dep)) => format!(
+ "**Resolved Dependency**\n\n**Types**: {}\n",
+ to_hover_text(type_dep)
+ ),
+ (None, None) => unreachable!("{}", json!(params)),
+ };
+ Some(Hover {
+ contents: HoverContents::Markup(MarkupContent {
+ kind: MarkupKind::Markdown,
+ value,
+ }),
+ range: Some(to_lsp_range(&range)),
+ })
} else {
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetQuickInfo((
specifier,
line_index.offset_tsc(params.text_document_position_params.position)?,
@@ -1207,7 +1129,7 @@ impl Inner {
error!("Unable to get quick info: {}", err);
LspError::internal_error()
})?;
- maybe_quick_info.map(|qi| qi.to_hover(&line_index))
+ maybe_quick_info.map(|qi| qi.to_hover(line_index))
};
self.performance.measure(mark);
Ok(hover)
@@ -1218,7 +1140,7 @@ impl Inner {
params: CodeActionParams,
) -> LspResult<Option<CodeActionResponse>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
@@ -1315,8 +1237,13 @@ impl Inner {
Some("deno-lint") => code_actions
.add_deno_lint_ignore_action(
&specifier,
- self.documents.get(&specifier),
diagnostic,
+ self.documents.text_info(&specifier),
+ self
+ .documents
+ .parsed_source(&specifier)
+ .map(|r| r.ok())
+ .flatten(),
)
.map_err(|err| {
error!("Unable to fix lint error: {}", err);
@@ -1485,7 +1412,7 @@ impl Inner {
params: CodeLensParams,
) -> LspResult<Option<Vec<CodeLens>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
|| !(self.config.get_workspace_settings().enabled_code_lens()
|| self.config.specifier_code_lens_test(&specifier))
@@ -1499,25 +1426,25 @@ impl Inner {
error!("Error getting code lenses for \"{}\": {}", specifier, err);
LspError::internal_error()
})?;
- let parsed_module = self
+ let parsed_source = self
.documents
- .get(&specifier)
- .map(|d| d.source().module())
- .flatten()
- .map(|m| m.as_ref().ok())
+ .parsed_source(&specifier)
+ .map(|r| r.ok())
.flatten();
- let line_index = self.get_line_index_sync(&specifier).ok_or_else(|| {
- error!(
- "Error getting code lenses for \"{}\": Missing line index",
- specifier
- );
- LspError::internal_error()
- })?;
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
+ "An unexpected specifier ({}) was provided.",
+ specifier
+ )))
+ },
+ Ok,
+ )?;
let code_lenses = code_lens::collect(
&specifier,
- parsed_module,
+ parsed_source,
&self.config,
- &line_index,
+ line_index,
&navigation_tree,
)
.await
@@ -1558,22 +1485,22 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("document_highlight", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let files_to_search = vec![specifier.clone()];
let req = tsc::RequestMethod::GetDocumentHighlights((
specifier,
@@ -1592,7 +1519,7 @@ impl Inner {
if let Some(document_highlights) = maybe_document_highlights {
let result = document_highlights
.into_iter()
- .map(|dh| dh.to_highlight(&line_index))
+ .map(|dh| dh.to_highlight(line_index.clone()))
.flatten()
.collect();
self.performance.measure(mark);
@@ -1610,22 +1537,22 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("references", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetReferences((
specifier,
line_index.offset_tsc(params.text_document_position.position)?,
@@ -1650,7 +1577,7 @@ impl Inner {
// TODO(lucacasonato): handle error correctly
let line_index =
self.get_line_index(reference_specifier).await.unwrap();
- results.push(reference.to_location(&line_index, self));
+ results.push(reference.to_location(line_index, self));
}
self.performance.measure(mark);
@@ -1668,22 +1595,22 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("goto_definition", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetDefinition((
specifier,
line_index.offset_tsc(params.text_document_position_params.position)?,
@@ -1698,7 +1625,7 @@ impl Inner {
})?;
if let Some(definition) = maybe_definition {
- let results = definition.to_definition(&line_index, self).await;
+ let results = definition.to_definition(line_index, self).await;
self.performance.measure(mark);
Ok(results)
} else {
@@ -1714,7 +1641,7 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
@@ -1735,15 +1662,15 @@ impl Inner {
{
Some(response)
} else {
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let trigger_character = if let Some(context) = &params.context {
context.trigger_character.clone()
} else {
@@ -1777,7 +1704,7 @@ impl Inner {
if let Some(completions) = maybe_completion_info {
let results = completions.as_completion_response(
- &line_index,
+ line_index,
&self.config.get_workspace_settings().suggest,
&specifier,
position,
@@ -1839,22 +1766,22 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("goto_implementation", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetImplementation((
specifier,
@@ -1872,7 +1799,9 @@ impl Inner {
let result = if let Some(implementations) = maybe_implementations {
let mut links = Vec::new();
for implementation in implementations {
- if let Some(link) = implementation.to_link(&line_index, self).await {
+ if let Some(link) =
+ implementation.to_link(line_index.clone(), self).await
+ {
links.push(link)
}
}
@@ -1890,22 +1819,22 @@ impl Inner {
params: FoldingRangeParams,
) -> LspResult<Option<Vec<FoldingRange>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("folding_range", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetOutliningSpans(specifier.clone());
let outlining_spans: Vec<tsc::OutliningSpan> = self
@@ -1930,7 +1859,7 @@ impl Inner {
.iter()
.map(|span| {
span.to_folding_range(
- &line_index,
+ line_index.clone(),
text_content.as_str().as_bytes(),
self.config.client_capabilities.line_folding_only,
)
@@ -1949,22 +1878,22 @@ impl Inner {
params: CallHierarchyIncomingCallsParams,
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
let specifier = self.url_map.normalize_url(&params.item.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("incoming_calls", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::ProvideCallHierarchyIncomingCalls((
specifier.clone(),
@@ -2005,22 +1934,22 @@ impl Inner {
params: CallHierarchyOutgoingCallsParams,
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
let specifier = self.url_map.normalize_url(&params.item.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("outgoing_calls", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::ProvideCallHierarchyOutgoingCalls((
specifier.clone(),
@@ -2044,7 +1973,7 @@ impl Inner {
for item in outgoing_calls.iter() {
if let Some(resolved) = item
.try_resolve_call_hierarchy_outgoing_call(
- &line_index,
+ line_index.clone(),
self,
maybe_root_path_owned.as_deref(),
)
@@ -2064,7 +1993,7 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
@@ -2073,15 +2002,15 @@ impl Inner {
let mark = self
.performance
.mark("prepare_call_hierarchy", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::PrepareCallHierarchy((
specifier.clone(),
@@ -2145,22 +2074,22 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("rename", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::FindRenameLocations {
specifier,
@@ -2240,22 +2169,22 @@ impl Inner {
params: SelectionRangeParams,
) -> LspResult<Option<Vec<SelectionRange>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("selection_range", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let mut selection_ranges = Vec::<SelectionRange>::new();
for position in params.positions {
@@ -2273,7 +2202,8 @@ impl Inner {
LspError::invalid_request()
})?;
- selection_ranges.push(selection_range.to_selection_range(&line_index));
+ selection_ranges
+ .push(selection_range.to_selection_range(line_index.clone()));
}
self.performance.measure(mark);
Ok(Some(selection_ranges))
@@ -2284,22 +2214,22 @@ impl Inner {
params: SemanticTokensParams,
) -> LspResult<Option<SemanticTokensResult>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("semantic_tokens_full", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let req = tsc::RequestMethod::GetEncodedSemanticClassifications((
specifier.clone(),
@@ -2318,7 +2248,7 @@ impl Inner {
})?;
let semantic_tokens: SemanticTokens =
- semantic_classification.to_semantic_tokens(&line_index);
+ semantic_classification.to_semantic_tokens(line_index);
let response = if !semantic_tokens.data.is_empty() {
Some(SemanticTokensResult::Tokens(semantic_tokens))
} else {
@@ -2333,7 +2263,7 @@ impl Inner {
params: SemanticTokensRangeParams,
) -> LspResult<Option<SemanticTokensRangeResult>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
@@ -2342,15 +2272,15 @@ impl Inner {
let mark = self
.performance
.mark("semantic_tokens_range", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let start = line_index.offset_tsc(params.range.start)?;
let length = line_index.offset_tsc(params.range.end)? - start;
@@ -2368,7 +2298,7 @@ impl Inner {
})?;
let semantic_tokens: SemanticTokens =
- semantic_classification.to_semantic_tokens(&line_index);
+ semantic_classification.to_semantic_tokens(line_index);
let response = if !semantic_tokens.data.is_empty() {
Some(SemanticTokensRangeResult::Tokens(semantic_tokens))
} else {
@@ -2385,22 +2315,22 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.documents.is_diagnosable(&specifier)
+ if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
return Ok(None);
}
let mark = self.performance.mark("signature_help", Some(&params));
- let line_index =
- if let Some(line_index) = self.get_line_index_sync(&specifier) {
- line_index
- } else {
- return Err(LspError::invalid_params(format!(
+ let line_index = self.get_line_index_sync(&specifier).map_or_else(
+ || {
+ Err(LspError::invalid_params(format!(
"An unexpected specifier ({}) was provided.",
specifier
- )));
- };
+ )))
+ },
+ Ok,
+ )?;
let options = if let Some(context) = params.context {
tsc::SignatureHelpItemsOptions {
trigger_reason: Some(tsc::SignatureHelpTriggerReason {
@@ -2658,7 +2588,7 @@ impl Inner {
params: lsp_custom::CacheParams,
) -> LspResult<Option<Value>> {
let referrer = self.url_map.normalize_url(&params.referrer.uri);
- if !self.documents.is_diagnosable(&referrer) {
+ if !self.is_diagnosable(&referrer) {
return Ok(None);
}
@@ -2689,10 +2619,7 @@ impl Inner {
// now that we have dependencies loaded, we need to re-analyze them and
// invalidate some diagnostics
- if self.documents.contains_key(&referrer) {
- self.analyze_dependencies(&referrer);
- self.diagnostics_server.invalidate(vec![referrer]).await;
- }
+ self.diagnostics_server.invalidate(vec![referrer]).await;
self.diagnostics_server.update().map_err(|err| {
error!("{}", err);
@@ -2731,12 +2658,15 @@ impl Inner {
.performance
.mark("virtual_text_document", Some(&params));
let specifier = self.url_map.normalize_url(&params.text_document.uri);
+ info!(
+ "virtual_text_document\n{}\nspecifier: {}",
+ json!(params),
+ specifier
+ );
let contents = if specifier.as_str() == "deno:/status.md" {
let mut contents = String::new();
- let mut documents_specifiers = self.documents.specifiers();
+ let mut documents_specifiers = self.documents.specifiers(false, false);
documents_specifiers.sort();
- let mut sources_specifiers = self.sources.specifiers();
- sources_specifiers.sort();
let measures = self.performance.to_vec();
let workspace_settings = self.config.get_workspace_settings();
@@ -2757,12 +2687,6 @@ impl Inner {
</details>
- - <details><summary>Sources in memory: {}</summary>
-
- - {}
-
- </details>
-
- <details><summary>Performance measures: {}</summary>
- {}
@@ -2770,18 +2694,12 @@ impl Inner {
</details>
"#,
serde_json::to_string_pretty(&workspace_settings).unwrap(),
- self.documents.len(),
+ documents_specifiers.len(),
documents_specifiers
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join("\n - "),
- self.sources.len(),
- sources_specifiers
- .into_iter()
- .map(|s| s.to_string())
- .collect::<Vec<String>>()
- .join("\n - "),
measures.len(),
measures
.iter()
@@ -2815,7 +2733,7 @@ impl Inner {
}
}
_ => {
- if let Some(source) = self.sources.get_source(&specifier) {
+ if let Some(source) = self.documents.content(&specifier) {
Some(source.to_string())
} else {
error!("The cached source was not found: {}", specifier);
diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs
index e6cc88208..725ca07b3 100644
--- a/cli/lsp/mod.rs
+++ b/cli/lsp/mod.rs
@@ -11,7 +11,6 @@ mod code_lens;
mod completions;
mod config;
mod diagnostics;
-mod document_source;
mod documents;
pub(crate) mod language_server;
mod lsp_custom;
@@ -20,8 +19,8 @@ mod path_to_regex;
mod performance;
mod refactor;
mod registries;
+mod resolver;
mod semantic_tokens;
-mod sources;
mod text;
mod tsc;
mod urls;
diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs
index c288666d5..504ff2738 100644
--- a/cli/lsp/registries.rs
+++ b/cli/lsp/registries.rs
@@ -424,7 +424,7 @@ impl ModuleRegistry {
/// For a string specifier from the client, provide a set of completions, if
/// any, for the specifier.
- pub async fn get_completions(
+ pub(crate) async fn get_completions(
&self,
current_specifier: &str,
offset: usize,
@@ -520,8 +520,8 @@ impl ModuleRegistry {
));
let command = if key.name == last_key_name
&& !state_snapshot
- .sources
- .contains_key(&item_specifier)
+ .documents
+ .contains_specifier(&item_specifier)
{
Some(lsp::Command {
title: "".to_string(),
@@ -610,8 +610,8 @@ impl ModuleRegistry {
);
let command = if k.name == last_key_name
&& !state_snapshot
- .sources
- .contains_key(&item_specifier)
+ .documents
+ .contains_specifier(&item_specifier)
{
Some(lsp::Command {
title: "".to_string(),
@@ -761,16 +761,14 @@ impl ModuleRegistry {
#[cfg(test)]
mod tests {
use super::*;
- use crate::lsp::documents::DocumentCache;
- use crate::lsp::sources::Sources;
+ use crate::lsp::documents::Documents;
use tempfile::TempDir;
fn mock_state_snapshot(
source_fixtures: &[(&str, &str)],
location: &Path,
) -> language_server::StateSnapshot {
- let documents = DocumentCache::default();
- let sources = Sources::new(location);
+ let documents = Documents::new(location);
let http_cache = HttpCache::new(location);
for (specifier, source) in source_fixtures {
let specifier =
@@ -779,13 +777,12 @@ mod tests {
.set(&specifier, HashMap::default(), source.as_bytes())
.expect("could not cache file");
assert!(
- sources.get_source(&specifier).is_some(),
+ documents.content(&specifier).is_some(),
"source could not be setup"
);
}
language_server::StateSnapshot {
documents,
- sources,
..Default::default()
}
}
diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs
new file mode 100644
index 000000000..4f768b697
--- /dev/null
+++ b/cli/lsp/resolver.rs
@@ -0,0 +1,33 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use deno_core::ModuleSpecifier;
+use deno_graph::source::Resolver;
+use import_map::ImportMap;
+use std::sync::Arc;
+
+#[derive(Debug)]
+pub(crate) struct ImportMapResolver(Arc<ImportMap>);
+
+impl ImportMapResolver {
+ pub fn new(import_map: Arc<ImportMap>) -> Self {
+ Self(import_map)
+ }
+
+ pub fn as_resolver(&self) -> &dyn Resolver {
+ self
+ }
+}
+
+impl Resolver for ImportMapResolver {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &ModuleSpecifier,
+ ) -> Result<ModuleSpecifier, AnyError> {
+ self
+ .0
+ .resolve(specifier, referrer.as_str())
+ .map_err(|err| err.into())
+ }
+}
diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs
deleted file mode 100644
index 69de6d976..000000000
--- a/cli/lsp/sources.rs
+++ /dev/null
@@ -1,797 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-use super::analysis;
-use super::document_source::DocumentSource;
-use super::text::LineIndex;
-use super::tsc;
-use super::urls::INVALID_SPECIFIER;
-
-use crate::file_fetcher::get_source_from_bytes;
-use crate::file_fetcher::map_content_type;
-use crate::file_fetcher::SUPPORTED_SCHEMES;
-use crate::http_cache;
-use crate::http_cache::HttpCache;
-use crate::text_encoding;
-
-use deno_ast::MediaType;
-use deno_core::error::anyhow;
-use deno_core::error::AnyError;
-use deno_core::parking_lot::Mutex;
-use deno_core::serde_json;
-use deno_core::ModuleSpecifier;
-use import_map::ImportMap;
-use std::collections::HashMap;
-use std::fs;
-use std::path::Path;
-use std::path::PathBuf;
-use std::sync::Arc;
-use std::time::SystemTime;
-use tsc::NavigationTree;
-
-fn get_remote_headers(
- cache_filename: &Path,
-) -> Option<HashMap<String, String>> {
- let metadata_path = http_cache::Metadata::filename(cache_filename);
- let metadata_str = fs::read_to_string(metadata_path).ok()?;
- let metadata: http_cache::Metadata =
- serde_json::from_str(&metadata_str).ok()?;
- Some(metadata.headers)
-}
-
-fn resolve_remote_specifier(
- specifier: &ModuleSpecifier,
- http_cache: &HttpCache,
- redirect_limit: isize,
-) -> Option<ModuleSpecifier> {
- let cache_filename = http_cache.get_cache_filename(specifier)?;
- if redirect_limit >= 0 && cache_filename.is_file() {
- let headers = get_remote_headers(&cache_filename)?;
- if let Some(location) = headers.get("location") {
- let redirect =
- deno_core::resolve_import(location, specifier.as_str()).ok()?;
- resolve_remote_specifier(&redirect, http_cache, redirect_limit - 1)
- } else {
- Some(specifier.clone())
- }
- } else {
- None
- }
-}
-
-fn resolve_specifier(
- specifier: &ModuleSpecifier,
- redirects: &mut HashMap<ModuleSpecifier, ModuleSpecifier>,
- http_cache: &HttpCache,
-) -> Option<ModuleSpecifier> {
- let scheme = specifier.scheme();
- if !SUPPORTED_SCHEMES.contains(&scheme) {
- return None;
- }
-
- if scheme == "data" {
- Some(specifier.clone())
- } else if scheme == "file" {
- let path = specifier.to_file_path().ok()?;
- if path.is_file() {
- Some(specifier.clone())
- } else {
- None
- }
- } else if let Some(specifier) = redirects.get(specifier) {
- Some(specifier.clone())
- } else {
- let redirect = resolve_remote_specifier(specifier, http_cache, 10)?;
- redirects.insert(specifier.clone(), redirect.clone());
- Some(redirect)
- }
-}
-
-#[derive(Debug, Clone)]
-struct Metadata {
- dependencies: Option<HashMap<String, analysis::Dependency>>,
- length_utf16: usize,
- maybe_navigation_tree: Option<tsc::NavigationTree>,
- maybe_types: Option<analysis::ResolvedDependency>,
- maybe_warning: Option<String>,
- media_type: MediaType,
- source: DocumentSource,
- specifier: ModuleSpecifier,
- version: String,
-}
-
-impl Default for Metadata {
- fn default() -> Self {
- Self {
- dependencies: None,
- length_utf16: 0,
- maybe_navigation_tree: None,
- maybe_types: None,
- maybe_warning: None,
- media_type: MediaType::default(),
- source: DocumentSource::new(
- &INVALID_SPECIFIER,
- MediaType::default(),
- Arc::new(String::default()),
- LineIndex::default(),
- ),
- specifier: INVALID_SPECIFIER.clone(),
- version: String::default(),
- }
- }
-}
-
-impl Metadata {
- fn new(
- specifier: &ModuleSpecifier,
- source: Arc<String>,
- version: &str,
- media_type: MediaType,
- maybe_warning: Option<String>,
- maybe_import_map: &Option<ImportMap>,
- ) -> Self {
- let line_index = LineIndex::new(&source);
- let document_source =
- DocumentSource::new(specifier, media_type, source, line_index);
- let (dependencies, maybe_types) =
- if let Some(Ok(parsed_module)) = document_source.module() {
- let (deps, maybe_types) = analysis::analyze_dependencies(
- specifier,
- media_type,
- parsed_module,
- maybe_import_map,
- );
- (Some(deps), maybe_types)
- } else {
- (None, None)
- };
-
- Self {
- dependencies,
- length_utf16: document_source
- .text_info()
- .text_str()
- .encode_utf16()
- .count(),
- maybe_navigation_tree: None,
- maybe_types,
- maybe_warning,
- media_type: media_type.to_owned(),
- source: document_source,
- specifier: specifier.clone(),
- version: version.to_string(),
- }
- }
-
- fn refresh(&mut self, maybe_import_map: &Option<ImportMap>) {
- let (dependencies, maybe_types) =
- if let Some(Ok(parsed_module)) = self.source.module() {
- let (deps, maybe_types) = analysis::analyze_dependencies(
- &self.specifier,
- self.media_type,
- parsed_module,
- maybe_import_map,
- );
- (Some(deps), maybe_types)
- } else {
- (None, None)
- };
- self.dependencies = dependencies;
- self.maybe_types = maybe_types;
- }
-}
-
-#[derive(Debug, Clone, Default)]
-struct Inner {
- http_cache: HttpCache,
- maybe_import_map: Option<ImportMap>,
- metadata: HashMap<ModuleSpecifier, Metadata>,
- redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
- remotes: HashMap<ModuleSpecifier, PathBuf>,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Sources(Arc<Mutex<Inner>>);
-
-impl Sources {
- pub fn new(location: &Path) -> Self {
- Self(Arc::new(Mutex::new(Inner::new(location))))
- }
-
- pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool {
- self.0.lock().contains_key(specifier)
- }
-
- pub fn get_line_index(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<LineIndex> {
- self.0.lock().get_line_index(specifier)
- }
-
- pub fn get_maybe_types(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<analysis::ResolvedDependency> {
- self.0.lock().get_maybe_types(specifier)
- }
-
- pub fn get_maybe_warning(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<String> {
- self.0.lock().get_maybe_warning(specifier)
- }
-
- pub fn get_media_type(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<MediaType> {
- self.0.lock().get_media_type(specifier)
- }
-
- pub fn get_navigation_tree(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<tsc::NavigationTree> {
- self.0.lock().get_navigation_tree(specifier)
- }
-
- pub fn get_script_version(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<String> {
- self.0.lock().get_script_version(specifier)
- }
-
- pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<Arc<String>> {
- self.0.lock().get_source(specifier)
- }
-
- pub fn len(&self) -> usize {
- self.0.lock().metadata.len()
- }
-
- pub fn resolve_import(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- ) -> Option<(ModuleSpecifier, MediaType)> {
- self.0.lock().resolve_import(specifier, referrer)
- }
-
- pub fn specifiers(&self) -> Vec<ModuleSpecifier> {
- self.0.lock().metadata.keys().cloned().collect()
- }
-
- pub fn set_import_map(&self, maybe_import_map: Option<ImportMap>) {
- self.0.lock().set_import_map(maybe_import_map)
- }
-
- pub fn set_navigation_tree(
- &self,
- specifier: &ModuleSpecifier,
- navigation_tree: tsc::NavigationTree,
- ) -> Result<(), AnyError> {
- self
- .0
- .lock()
- .set_navigation_tree(specifier, navigation_tree)
- }
-}
-
-impl Inner {
- fn new(location: &Path) -> Self {
- Self {
- http_cache: HttpCache::new(location),
- ..Default::default()
- }
- }
-
- fn calculate_script_version(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<String> {
- let path = self.get_path(specifier)?;
- let metadata = fs::metadata(path).ok()?;
- if let Ok(modified) = metadata.modified() {
- if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH) {
- Some(format!("{}", n.as_millis()))
- } else {
- Some("1".to_string())
- }
- } else {
- Some("1".to_string())
- }
- }
-
- fn contains_key(&mut self, specifier: &ModuleSpecifier) -> bool {
- if let Some(specifier) =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)
- {
- if self.get_metadata(&specifier).is_some() {
- return true;
- }
- }
- false
- }
-
- fn get_line_index(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<LineIndex> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&specifier)?;
- Some(metadata.source.line_index().clone())
- }
-
- fn get_maybe_types(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<analysis::ResolvedDependency> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&specifier)?;
- metadata.maybe_types
- }
-
- fn get_maybe_warning(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<String> {
- let metadata = self.get_metadata(specifier)?;
- metadata.maybe_warning
- }
-
- fn get_media_type(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<MediaType> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&specifier)?;
- Some(metadata.media_type)
- }
-
- fn get_metadata(&mut self, specifier: &ModuleSpecifier) -> Option<Metadata> {
- if let Some(metadata) = self.metadata.get(specifier).cloned() {
- if metadata.version == self.calculate_script_version(specifier)? {
- return Some(metadata);
- }
- }
-
- let version = self.calculate_script_version(specifier)?;
- let path = self.get_path(specifier)?;
- let bytes = fs::read(path).ok()?;
- let scheme = specifier.scheme();
- let (source, media_type, maybe_types, maybe_warning) = if scheme == "file" {
- let maybe_charset =
- Some(text_encoding::detect_charset(&bytes).to_string());
- let source = get_source_from_bytes(bytes, maybe_charset).ok()?;
- (source, MediaType::from(specifier), None, None)
- } else {
- let cache_filename = self.http_cache.get_cache_filename(specifier)?;
- let headers = get_remote_headers(&cache_filename)?;
- let maybe_content_type = headers.get("content-type").cloned();
- let (media_type, maybe_charset) =
- map_content_type(specifier, maybe_content_type);
- let source = get_source_from_bytes(bytes, maybe_charset).ok()?;
- let maybe_types = headers.get("x-typescript-types").map(|s| {
- analysis::resolve_import(s, specifier, &self.maybe_import_map)
- });
- let maybe_warning = headers.get("x-deno-warning").cloned();
- (source, media_type, maybe_types, maybe_warning)
- };
- let mut metadata = Metadata::new(
- specifier,
- Arc::new(source),
- &version,
- media_type,
- maybe_warning,
- &self.maybe_import_map,
- );
- if maybe_types.is_some() {
- metadata.maybe_types = maybe_types;
- }
- self.metadata.insert(specifier.clone(), metadata.clone());
- Some(metadata)
- }
-
- fn get_navigation_tree(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<tsc::NavigationTree> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&specifier)?;
- metadata.maybe_navigation_tree
- }
-
- fn get_path(&mut self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
- if specifier.scheme() == "file" {
- specifier.to_file_path().ok()
- } else if let Some(path) = self.remotes.get(specifier) {
- Some(path.clone())
- } else {
- let path = self.http_cache.get_cache_filename(specifier)?;
- if path.is_file() {
- self.remotes.insert(specifier.clone(), path.clone());
- Some(path)
- } else {
- None
- }
- }
- }
-
- fn get_script_version(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<String> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&specifier)?;
- Some(metadata.version)
- }
-
- fn get_source(&mut self, specifier: &ModuleSpecifier) -> Option<Arc<String>> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&specifier)?;
- Some(metadata.source.text_info().text())
- }
-
- fn resolution_result(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<(ModuleSpecifier, MediaType)> {
- let specifier =
- resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
- let media_type = if let Some(metadata) = self.metadata.get(&specifier) {
- metadata.media_type
- } else {
- MediaType::from(&specifier)
- };
- Some((specifier, media_type))
- }
-
- fn resolve_import(
- &mut self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- ) -> Option<(ModuleSpecifier, MediaType)> {
- let referrer =
- resolve_specifier(referrer, &mut self.redirects, &self.http_cache)?;
- let metadata = self.get_metadata(&referrer)?;
- let dependencies = &metadata.dependencies?;
- let dependency = dependencies.get(specifier)?;
- if let Some(type_dependency) = &dependency.maybe_type {
- if let analysis::ResolvedDependency::Resolved(resolved_specifier) =
- type_dependency
- {
- // even if we have a module in the maybe_types slot, it doesn't mean
- // that it is the actual module we should be using based on headers,
- // so we check here and update properly.
- if let Some(type_dependency) = self.get_maybe_types(resolved_specifier)
- {
- self.set_maybe_type(specifier, &referrer, &type_dependency);
- if let analysis::ResolvedDependency::Resolved(type_specifier) =
- type_dependency
- {
- self.resolution_result(&type_specifier)
- } else {
- self.resolution_result(resolved_specifier)
- }
- } else {
- self.resolution_result(resolved_specifier)
- }
- } else {
- None
- }
- } else {
- let code_dependency = &dependency.maybe_code.clone()?;
- if let analysis::ResolvedDependency::Resolved(resolved_specifier) =
- code_dependency
- {
- if let Some(type_dependency) = self.get_maybe_types(resolved_specifier)
- {
- self.set_maybe_type(specifier, &referrer, &type_dependency);
- if let analysis::ResolvedDependency::Resolved(type_specifier) =
- type_dependency
- {
- self.resolution_result(&type_specifier)
- } else {
- self.resolution_result(resolved_specifier)
- }
- } else {
- self.resolution_result(resolved_specifier)
- }
- } else {
- None
- }
- }
- }
-
- fn set_import_map(&mut self, maybe_import_map: Option<ImportMap>) {
- for (_, metadata) in self.metadata.iter_mut() {
- metadata.refresh(&maybe_import_map);
- }
- self.maybe_import_map = maybe_import_map;
- }
-
- fn set_maybe_type(
- &mut self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- dependency: &analysis::ResolvedDependency,
- ) {
- if let Some(metadata) = self.metadata.get_mut(referrer) {
- if let Some(dependencies) = &mut metadata.dependencies {
- if let Some(dep) = dependencies.get_mut(specifier) {
- dep.maybe_type = Some(dependency.clone());
- }
- }
- }
- }
-
- fn set_navigation_tree(
- &mut self,
- specifier: &ModuleSpecifier,
- navigation_tree: NavigationTree,
- ) -> Result<(), AnyError> {
- let mut metadata = self
- .metadata
- .get_mut(specifier)
- .ok_or_else(|| anyhow!("Specifier not found {}"))?;
- metadata.maybe_navigation_tree = Some(navigation_tree);
- Ok(())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use deno_core::resolve_path;
- use deno_core::resolve_url;
- use deno_core::serde_json::json;
- use tempfile::TempDir;
-
- fn setup() -> (Sources, PathBuf) {
- let temp_dir = TempDir::new().expect("could not create temp dir");
- let location = temp_dir.path().join("deps");
- let sources = Sources::new(&location);
- (sources, location)
- }
-
- #[test]
- fn test_sources_get_script_version() {
- let (sources, _) = setup();
- let tests = test_util::testdata_path();
- let specifier =
- resolve_path(&tests.join("001_hello.js").to_string_lossy()).unwrap();
- let actual = sources.get_script_version(&specifier);
- assert!(actual.is_some());
- }
-
- #[test]
- fn test_sources_get_text() {
- let (sources, _) = setup();
- let tests = test_util::testdata_path();
- let specifier =
- resolve_path(&tests.join("001_hello.js").to_string_lossy()).unwrap();
- let actual = sources.get_source(&specifier);
- assert!(actual.is_some());
- let actual = actual.unwrap().to_string();
- assert_eq!(actual, "console.log(\"Hello World\");\n");
- }
-
- #[test]
- fn test_resolve_dependency_types() {
- let (sources, location) = setup();
- let cache = HttpCache::new(&location);
- let specifier_dep = resolve_url("https://deno.land/x/mod.ts").unwrap();
- cache
- .set(
- &specifier_dep,
- Default::default(),
- b"export * from \"https://deno.land/x/lib.js\";",
- )
- .unwrap();
- let specifier_code = resolve_url("https://deno.land/x/lib.js").unwrap();
- let mut headers_code = HashMap::new();
- headers_code
- .insert("x-typescript-types".to_string(), "./lib.d.ts".to_string());
- cache
- .set(&specifier_code, headers_code, b"export const a = 1;")
- .unwrap();
- let specifier_type = resolve_url("https://deno.land/x/lib.d.ts").unwrap();
- cache
- .set(
- &specifier_type,
- Default::default(),
- b"export const a: number;",
- )
- .unwrap();
- let actual =
- sources.resolve_import("https://deno.land/x/lib.js", &specifier_dep);
- assert_eq!(actual, Some((specifier_type, MediaType::Dts)))
- }
-
- #[test]
- /// This is a regression test for https://github.com/denoland/deno/issues/10031
- fn test_resolve_dependency_import_types() {
- let (sources, location) = setup();
- let cache = HttpCache::new(&location);
- let specifier_dep = resolve_url("https://deno.land/x/mod.ts").unwrap();
- cache
- .set(
- &specifier_dep,
- Default::default(),
- b"import type { A } from \"https://deno.land/x/lib.js\";\nconst a: A = { a: \"a\" };",
- )
- .unwrap();
- let specifier_code = resolve_url("https://deno.land/x/lib.js").unwrap();
- let mut headers_code = HashMap::new();
- headers_code
- .insert("x-typescript-types".to_string(), "./lib.d.ts".to_string());
- cache
- .set(&specifier_code, headers_code, b"export const a = 1;")
- .unwrap();
- let specifier_type = resolve_url("https://deno.land/x/lib.d.ts").unwrap();
- cache
- .set(
- &specifier_type,
- Default::default(),
- b"export const a: number;\nexport interface A { a: number; }\n",
- )
- .unwrap();
- let actual =
- sources.resolve_import("https://deno.land/x/lib.js", &specifier_dep);
- assert_eq!(actual, Some((specifier_type, MediaType::Dts)))
- }
-
- #[test]
- fn test_warning_header() {
- let (sources, location) = setup();
- let cache = HttpCache::new(&location);
- let specifier = resolve_url("https://deno.land/x/lib.js").unwrap();
- let mut headers = HashMap::new();
- headers.insert(
- "x-deno-warning".to_string(),
- "this is a warning".to_string(),
- );
- cache
- .set(&specifier, headers, b"export const a = 1;")
- .unwrap();
- let actual = sources.get_maybe_warning(&specifier);
- assert_eq!(actual, Some("this is a warning".to_string()));
- }
-
- #[test]
- fn test_resolve_dependency_evil_redirect() {
- let (sources, location) = setup();
- let cache = HttpCache::new(&location);
- let evil_specifier = resolve_url("https://deno.land/x/evil.ts").unwrap();
- let mut evil_headers = HashMap::new();
- evil_headers
- .insert("location".to_string(), "file:///etc/passwd".to_string());
- cache.set(&evil_specifier, evil_headers, b"").unwrap();
- let remote_specifier = resolve_url("https://deno.land/x/mod.ts").unwrap();
- cache
- .set(
- &remote_specifier,
- Default::default(),
- b"export * from \"./evil.ts\";",
- )
- .unwrap();
- let actual = sources.resolve_import("./evil.ts", &remote_specifier);
- assert_eq!(actual, None);
- }
-
- #[test]
- fn test_resolve_with_import_map() {
- let (sources, location) = setup();
- let import_map_json = json!({
- "imports": {
- "mylib": "https://deno.land/x/myLib/index.js"
- }
- });
- let import_map = ImportMap::from_json(
- "https://deno.land/x/",
- &import_map_json.to_string(),
- )
- .unwrap();
- sources.set_import_map(Some(import_map));
- let cache = HttpCache::new(&location);
- let mylib_specifier =
- resolve_url("https://deno.land/x/myLib/index.js").unwrap();
- let mut mylib_headers_map = HashMap::new();
- mylib_headers_map.insert(
- "content-type".to_string(),
- "application/javascript".to_string(),
- );
- cache
- .set(
- &mylib_specifier,
- mylib_headers_map,
- b"export const a = \"a\";\n",
- )
- .unwrap();
- let referrer = resolve_url("https://deno.land/x/mod.ts").unwrap();
- cache
- .set(
- &referrer,
- Default::default(),
- b"export { a } from \"mylib\";",
- )
- .unwrap();
- let actual = sources.resolve_import("mylib", &referrer);
- assert_eq!(actual, Some((mylib_specifier, MediaType::JavaScript)));
- }
-
- #[test]
- fn test_update_import_map() {
- let (sources, location) = setup();
- let import_map_json = json!({
- "imports": {
- "otherlib": "https://deno.land/x/otherlib/index.js"
- }
- });
- let import_map = ImportMap::from_json(
- "https://deno.land/x/",
- &import_map_json.to_string(),
- )
- .unwrap();
- sources.set_import_map(Some(import_map));
- let cache = HttpCache::new(&location);
- let mylib_specifier =
- resolve_url("https://deno.land/x/myLib/index.js").unwrap();
- let mut mylib_headers_map = HashMap::new();
- mylib_headers_map.insert(
- "content-type".to_string(),
- "application/javascript".to_string(),
- );
- cache
- .set(
- &mylib_specifier,
- mylib_headers_map,
- b"export const a = \"a\";\n",
- )
- .unwrap();
- let referrer = resolve_url("https://deno.land/x/mod.ts").unwrap();
- cache
- .set(
- &referrer,
- Default::default(),
- b"export { a } from \"mylib\";",
- )
- .unwrap();
- let actual = sources.resolve_import("mylib", &referrer);
- assert_eq!(actual, None);
- let import_map_json = json!({
- "imports": {
- "otherlib": "https://deno.land/x/otherlib/index.js",
- "mylib": "https://deno.land/x/myLib/index.js"
- }
- });
- let import_map = ImportMap::from_json(
- "https://deno.land/x/",
- &import_map_json.to_string(),
- )
- .unwrap();
- sources.set_import_map(Some(import_map));
- let actual = sources.resolve_import("mylib", &referrer);
- assert_eq!(actual, Some((mylib_specifier, MediaType::JavaScript)));
- }
-
- #[test]
- fn test_sources_resolve_specifier_non_supported_schema() {
- let (sources, _) = setup();
- let specifier =
- resolve_url("foo://a/b/c.ts").expect("could not create specifier");
- let sources = sources.0.lock();
- let mut redirects = sources.redirects.clone();
- let http_cache = sources.http_cache.clone();
- let actual = resolve_specifier(&specifier, &mut redirects, &http_cache);
- assert!(actual.is_none());
- }
-}
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 9899fad72..8f3db6131 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -1,7 +1,5 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-use super::analysis::ResolvedDependency;
-use super::analysis::ResolvedDependencyErr;
use super::code_lens;
use super::config;
use super::language_server;
@@ -22,7 +20,6 @@ use crate::tokio_util::create_basic_runtime;
use crate::tsc;
use crate::tsc::ResolveArgs;
-use deno_ast::MediaType;
use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
@@ -106,7 +103,7 @@ impl TsServer {
Self(tx)
}
- pub async fn request<R>(
+ pub(crate) async fn request<R>(
&self,
snapshot: StateSnapshot,
req: RequestMethod,
@@ -128,8 +125,8 @@ impl TsServer {
pub struct AssetDocument {
pub text: Arc<String>,
pub length: usize,
- pub line_index: LineIndex,
- pub maybe_navigation_tree: Option<NavigationTree>,
+ pub line_index: Arc<LineIndex>,
+ pub maybe_navigation_tree: Option<Arc<NavigationTree>>,
}
impl AssetDocument {
@@ -138,7 +135,7 @@ impl AssetDocument {
Self {
text: Arc::new(text.to_string()),
length: text.encode_utf16().count(),
- line_index: LineIndex::new(text),
+ line_index: Arc::new(LineIndex::new(text)),
maybe_navigation_tree: None,
}
}
@@ -179,10 +176,18 @@ impl Assets {
self.0.insert(k, v)
}
+ pub fn get_navigation_tree(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<Arc<NavigationTree>> {
+ let doc = self.0.get(specifier).map(|v| v.as_ref()).flatten()?;
+ doc.maybe_navigation_tree.as_ref().cloned()
+ }
+
pub fn set_navigation_tree(
&mut self,
specifier: &ModuleSpecifier,
- navigation_tree: NavigationTree,
+ navigation_tree: Arc<NavigationTree>,
) -> Result<(), AnyError> {
let maybe_doc = self
.0
@@ -199,7 +204,7 @@ impl Assets {
/// Optionally returns an internal asset, first checking for any static assets
/// in Rust, then checking any previously retrieved static assets from the
/// isolate, and then finally, the tsc isolate itself.
-pub async fn get_asset(
+pub(crate) async fn get_asset(
specifier: &ModuleSpecifier,
ts_server: &TsServer,
state_snapshot: StateSnapshot,
@@ -498,7 +503,7 @@ pub struct TextSpan {
}
impl TextSpan {
- pub fn to_range(&self, line_index: &LineIndex) -> lsp::Range {
+ pub fn to_range(&self, line_index: Arc<LineIndex>) -> lsp::Range {
lsp::Range {
start: line_index.position_tsc(self.start.into()),
end: line_index.position_tsc(TextSize::from(self.start + self.length)),
@@ -532,7 +537,7 @@ pub struct QuickInfo {
}
impl QuickInfo {
- pub fn to_hover(&self, line_index: &LineIndex) -> lsp::Hover {
+ pub fn to_hover(&self, line_index: Arc<LineIndex>) -> lsp::Hover {
let mut contents = Vec::<lsp::MarkedString>::new();
if let Some(display_string) = self
.display_parts
@@ -585,7 +590,7 @@ pub struct DocumentSpan {
impl DocumentSpan {
pub(crate) async fn to_link(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
) -> Option<lsp::LocationLink> {
let target_specifier = normalize_specifier(&self.file_name).ok()?;
@@ -600,13 +605,13 @@ impl DocumentSpan {
let (target_range, target_selection_range) =
if let Some(context_span) = &self.context_span {
(
- context_span.to_range(&target_line_index),
- self.text_span.to_range(&target_line_index),
+ context_span.to_range(target_line_index.clone()),
+ self.text_span.to_range(target_line_index),
)
} else {
(
- self.text_span.to_range(&target_line_index),
- self.text_span.to_range(&target_line_index),
+ self.text_span.to_range(target_line_index.clone()),
+ self.text_span.to_range(target_line_index),
)
};
let origin_selection_range =
@@ -642,7 +647,7 @@ pub struct NavigationTree {
impl NavigationTree {
pub fn to_code_lens(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
specifier: &ModuleSpecifier,
source: &code_lens::CodeLensSource,
) -> lsp::CodeLens {
@@ -666,7 +671,7 @@ impl NavigationTree {
pub fn collect_document_symbols(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
document_symbols: &mut Vec<lsp::DocumentSymbol>,
) -> bool {
let mut should_include = self.should_include_entry();
@@ -692,8 +697,8 @@ impl NavigationTree {
})
.any(|child_range| range.intersect(child_range).is_some());
if should_traverse_child {
- let included_child =
- child.collect_document_symbols(line_index, &mut symbol_children);
+ let included_child = child
+ .collect_document_symbols(line_index.clone(), &mut symbol_children);
should_include = should_include || included_child;
}
}
@@ -727,8 +732,8 @@ impl NavigationTree {
document_symbols.push(lsp::DocumentSymbol {
name: self.text.clone(),
kind: self.kind.clone().into(),
- range: span.to_range(line_index),
- selection_range: selection_span.to_range(line_index),
+ range: span.to_range(line_index.clone()),
+ selection_range: selection_span.to_range(line_index.clone()),
tags,
children,
detail: None,
@@ -786,7 +791,7 @@ pub struct ImplementationLocation {
impl ImplementationLocation {
pub(crate) fn to_location(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
) -> lsp::Location {
let specifier = normalize_specifier(&self.document_span.file_name)
@@ -803,7 +808,7 @@ impl ImplementationLocation {
pub(crate) async fn to_link(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
) -> Option<lsp::LocationLink> {
self
@@ -846,7 +851,7 @@ impl RenameLocations {
lsp::TextDocumentEdit {
text_document: lsp::OptionalVersionedTextDocumentIdentifier {
uri: uri.clone(),
- version: language_server.document_version(specifier.clone()),
+ version: language_server.document_version(&specifier),
},
edits:
Vec::<lsp::OneOf<lsp::TextEdit, lsp::AnnotatedTextEdit>>::new(),
@@ -860,7 +865,7 @@ impl RenameLocations {
range: location
.document_span
.text_span
- .to_range(&language_server.get_line_index(specifier.clone()).await?),
+ .to_range(language_server.get_line_index(specifier.clone()).await?),
new_text: new_name.to_string(),
}));
}
@@ -919,14 +924,16 @@ pub struct DefinitionInfoAndBoundSpan {
impl DefinitionInfoAndBoundSpan {
pub(crate) async fn to_definition(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
) -> Option<lsp::GotoDefinitionResponse> {
if let Some(definitions) = &self.definitions {
let mut location_links = Vec::<lsp::LocationLink>::new();
for di in definitions {
- if let Some(link) =
- di.document_span.to_link(line_index, language_server).await
+ if let Some(link) = di
+ .document_span
+ .to_link(line_index.clone(), language_server)
+ .await
{
location_links.push(link);
}
@@ -948,13 +955,13 @@ pub struct DocumentHighlights {
impl DocumentHighlights {
pub fn to_highlight(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
) -> Vec<lsp::DocumentHighlight> {
self
.highlight_spans
.iter()
.map(|hs| lsp::DocumentHighlight {
- range: hs.text_span.to_range(line_index),
+ range: hs.text_span.to_range(line_index.clone()),
kind: match hs.kind {
HighlightSpanKind::WrittenReference => {
Some(lsp::DocumentHighlightKind::Write)
@@ -976,7 +983,7 @@ pub struct TextChange {
impl TextChange {
pub fn as_text_edit(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
) -> lsp::OneOf<lsp::TextEdit, lsp::AnnotatedTextEdit> {
lsp::OneOf::Left(lsp::TextEdit {
range: self.span.to_range(line_index),
@@ -1004,12 +1011,12 @@ impl FileTextChanges {
let edits = self
.text_changes
.iter()
- .map(|tc| tc.as_text_edit(&line_index))
+ .map(|tc| tc.as_text_edit(line_index.clone()))
.collect();
Ok(lsp::TextDocumentEdit {
text_document: lsp::OptionalVersionedTextDocumentIdentifier {
uri: specifier.clone(),
- version: language_server.document_version(specifier),
+ version: language_server.document_version(&specifier),
},
edits,
})
@@ -1024,7 +1031,7 @@ impl FileTextChanges {
let line_index = if !self.is_new_file.unwrap_or(false) {
language_server.get_line_index(specifier.clone()).await?
} else {
- LineIndex::new("")
+ Arc::new(LineIndex::new(""))
};
if self.is_new_file.unwrap_or(false) {
@@ -1043,12 +1050,12 @@ impl FileTextChanges {
let edits = self
.text_changes
.iter()
- .map(|tc| tc.as_text_edit(&line_index))
+ .map(|tc| tc.as_text_edit(line_index.clone()))
.collect();
ops.push(lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
text_document: lsp::OptionalVersionedTextDocumentIdentifier {
uri: specifier.clone(),
- version: language_server.document_version(specifier),
+ version: language_server.document_version(&specifier),
},
edits,
}));
@@ -1066,7 +1073,7 @@ pub struct Classifications {
impl Classifications {
pub fn to_semantic_tokens(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
) -> lsp::SemanticTokens {
let token_count = self.spans.len() / 3;
let mut builder = SemanticTokensBuilder::new();
@@ -1299,7 +1306,7 @@ pub struct ReferenceEntry {
impl ReferenceEntry {
pub(crate) fn to_location(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
) -> lsp::Location {
let specifier = normalize_specifier(&self.document_span.file_name)
@@ -1342,7 +1349,7 @@ impl CallHierarchyItem {
.ok()?;
Some(self.to_call_hierarchy_item(
- &target_line_index,
+ target_line_index,
language_server,
maybe_root_path,
))
@@ -1350,7 +1357,7 @@ impl CallHierarchyItem {
pub(crate) fn to_call_hierarchy_item(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> lsp::CallHierarchyItem {
@@ -1410,7 +1417,7 @@ impl CallHierarchyItem {
uri,
detail: Some(detail),
kind: self.kind.clone().into(),
- range: self.span.to_range(line_index),
+ range: self.span.to_range(line_index.clone()),
selection_range: self.selection_span.to_range(line_index),
data: None,
}
@@ -1444,14 +1451,14 @@ impl CallHierarchyIncomingCall {
Some(lsp::CallHierarchyIncomingCall {
from: self.from.to_call_hierarchy_item(
- &target_line_index,
+ target_line_index.clone(),
language_server,
maybe_root_path,
),
from_ranges: self
.from_spans
.iter()
- .map(|span| span.to_range(&target_line_index))
+ .map(|span| span.to_range(target_line_index.clone()))
.collect(),
})
}
@@ -1467,7 +1474,7 @@ pub struct CallHierarchyOutgoingCall {
impl CallHierarchyOutgoingCall {
pub(crate) async fn try_resolve_call_hierarchy_outgoing_call(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyOutgoingCall> {
@@ -1479,14 +1486,14 @@ impl CallHierarchyOutgoingCall {
Some(lsp::CallHierarchyOutgoingCall {
to: self.to.to_call_hierarchy_item(
- &target_line_index,
+ target_line_index,
language_server,
maybe_root_path,
),
from_ranges: self
.from_spans
.iter()
- .map(|span| span.to_range(line_index))
+ .map(|span| span.to_range(line_index.clone()))
.collect(),
})
}
@@ -1560,7 +1567,7 @@ pub struct CompletionInfo {
impl CompletionInfo {
pub fn as_completion_response(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
settings: &config::CompletionSettings,
specifier: &ModuleSpecifier,
position: u32,
@@ -1569,8 +1576,13 @@ impl CompletionInfo {
.entries
.iter()
.map(|entry| {
- entry
- .as_completion_item(line_index, self, settings, specifier, position)
+ entry.as_completion_item(
+ line_index.clone(),
+ self,
+ settings,
+ specifier,
+ position,
+ )
})
.collect();
let is_incomplete = self
@@ -1711,7 +1723,7 @@ impl CompletionEntry {
pub fn as_completion_item(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
info: &CompletionInfo,
settings: &config::CompletionSettings,
specifier: &ModuleSpecifier,
@@ -1837,11 +1849,11 @@ const FOLD_END_PAIR_CHARACTERS: &[u8] = &[b'}', b']', b')', b'`'];
impl OutliningSpan {
pub fn to_folding_range(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
content: &[u8],
line_folding_only: bool,
) -> lsp::FoldingRange {
- let range = self.text_span.to_range(line_index);
+ let range = self.text_span.to_range(line_index.clone());
lsp::FoldingRange {
start_line: range.start.line,
start_character: if line_folding_only {
@@ -1867,7 +1879,7 @@ impl OutliningSpan {
fn adjust_folding_end_line(
&self,
range: &lsp::Range,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
content: &[u8],
line_folding_only: bool,
) -> u32 {
@@ -1991,10 +2003,10 @@ pub struct SelectionRange {
impl SelectionRange {
pub fn to_selection_range(
&self,
- line_index: &LineIndex,
+ line_index: Arc<LineIndex>,
) -> lsp::SelectionRange {
lsp::SelectionRange {
- range: self.text_span.to_range(line_index),
+ range: self.text_span.to_range(line_index.clone()),
parent: self.parent.as_ref().map(|parent_selection| {
Box::new(parent_selection.to_selection_range(line_index))
}),
@@ -2065,19 +2077,13 @@ fn cache_snapshot(
.snapshots
.contains_key(&(specifier.clone(), version.clone().into()))
{
- let content = if state.state_snapshot.documents.contains_key(specifier) {
- state
- .state_snapshot
- .documents
- .content(specifier)
- .ok_or_else(|| {
- anyhow!("Specifier unexpectedly doesn't have content: {}", specifier)
- })?
- } else {
- state.state_snapshot.sources.get_source(specifier).ok_or_else(|| {
- anyhow!("Specifier (\"{}\") is not an in memory document or on disk resource.", specifier)
- })?
- };
+ let content = state
+ .state_snapshot
+ .documents
+ .content(specifier)
+ .ok_or_else(|| {
+ anyhow!("Specifier unexpectedly doesn't have content: {}", specifier)
+ })?;
state
.snapshots
.insert((specifier.clone(), version.into()), content.to_string());
@@ -2140,7 +2146,10 @@ fn op_exists(state: &mut State, args: SpecifierArgs) -> Result<bool, AnyError> {
.performance
.mark("op_exists", Some(&args));
let specifier = state.normalize_specifier(args.specifier)?;
- let result = state.state_snapshot.sources.contains_key(&specifier);
+ let result = state
+ .state_snapshot
+ .documents
+ .contains_specifier(&specifier);
state.state_snapshot.performance.measure(mark);
Ok(result)
}
@@ -2268,7 +2277,7 @@ fn op_load(
.performance
.mark("op_load", Some(&args));
let specifier = state.normalize_specifier(args.specifier)?;
- let result = state.state_snapshot.sources.get_source(&specifier);
+ let result = state.state_snapshot.documents.content(&specifier);
state.state_snapshot.performance.measure(mark);
Ok(result.map(|t| t.to_string()))
}
@@ -2281,89 +2290,33 @@ fn op_resolve(
.state_snapshot
.performance
.mark("op_resolve", Some(&args));
- let mut resolved = Vec::new();
let referrer = state.normalize_specifier(&args.base)?;
- let sources = &mut state.state_snapshot.sources;
- if state.state_snapshot.documents.contains_key(&referrer) {
- if let Some(dependencies) =
- state.state_snapshot.documents.dependencies(&referrer)
- {
- for specifier in &args.specifiers {
- if specifier.starts_with("asset:///") {
- resolved.push(Some((
- specifier.clone(),
- MediaType::from(specifier).as_ts_extension().into(),
- )))
- } else if let Some(dependency) = dependencies.get(specifier) {
- let resolved_import =
- if let Some(resolved_import) = &dependency.maybe_type {
- resolved_import.clone()
- } else if let Some(resolved_import) = &dependency.maybe_code {
- resolved_import.clone()
- } else {
- ResolvedDependency::Err(ResolvedDependencyErr::Missing)
- };
- if let ResolvedDependency::Resolved(resolved_specifier) =
- resolved_import
- {
- if state
- .state_snapshot
- .documents
- .contains_key(&resolved_specifier)
- {
- let media_type = MediaType::from(&resolved_specifier);
- resolved.push(Some((
- resolved_specifier.to_string(),
- media_type.as_ts_extension().into(),
- )));
- } else if sources.contains_key(&resolved_specifier) {
- let media_type = if let Some(media_type) =
- sources.get_media_type(&resolved_specifier)
- {
- media_type
- } else {
- MediaType::from(&resolved_specifier)
- };
- resolved.push(Some((
- resolved_specifier.to_string(),
- media_type.as_ts_extension().into(),
- )));
- } else {
- resolved.push(None);
- }
- } else {
- resolved.push(None);
- }
- }
- }
- }
- } else if sources.contains_key(&referrer) {
- for specifier in &args.specifiers {
- if let Some((resolved_specifier, media_type)) =
- sources.resolve_import(specifier, &referrer)
- {
- resolved.push(Some((
- resolved_specifier.to_string(),
- media_type.as_ts_extension().into(),
- )));
- } else {
- resolved.push(None);
- }
- }
+ let result = if let Some(resolved) = state
+ .state_snapshot
+ .documents
+ .resolve(args.specifiers, &referrer)
+ {
+ Ok(
+ resolved
+ .into_iter()
+ .map(|o| {
+ o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
+ })
+ .collect(),
+ )
} else {
- state.state_snapshot.performance.measure(mark);
- return Err(custom_error(
+ Err(custom_error(
"NotFound",
format!(
- "the referring ({}) specifier is unexpectedly missing",
- referrer
+ "Error resolving. Referring specifier \"{}\" was not found.",
+ args.base
),
- ));
- }
+ ))
+ };
state.state_snapshot.performance.measure(mark);
- Ok(resolved)
+ result
}
fn op_respond(state: &mut State, args: Response) -> Result<bool, AnyError> {
@@ -2375,15 +2328,7 @@ fn op_script_names(
state: &mut State,
_args: Value,
) -> Result<Vec<ModuleSpecifier>, AnyError> {
- Ok(
- state
- .state_snapshot
- .documents
- .open_specifiers()
- .into_iter()
- .cloned()
- .collect(),
- )
+ Ok(state.state_snapshot.documents.specifiers(true, true))
}
#[derive(Debug, Deserialize, Serialize)]
@@ -2407,17 +2352,8 @@ fn op_script_version(
} else {
Ok(None)
}
- } else if let Some(version) =
- state.state_snapshot.documents.version(&specifier)
- {
- Ok(Some(version.to_string()))
} else {
- let sources = &mut state.state_snapshot.sources;
- if let Some(version) = sources.get_script_version(&specifier) {
- Ok(Some(version))
- } else {
- Ok(None)
- }
+ Ok(state.state_snapshot.documents.version(&specifier))
};
state.state_snapshot.performance.measure(mark);
@@ -2868,7 +2804,7 @@ impl RequestMethod {
}
/// Send a request into a runtime and return the JSON value of the response.
-pub fn request(
+pub(crate) fn request(
runtime: &mut JsRuntime,
state_snapshot: StateSnapshot,
method: RequestMethod,
@@ -2908,10 +2844,8 @@ mod tests {
use super::*;
use crate::http_cache::HttpCache;
use crate::http_util::HeadersMap;
- use crate::lsp::analysis;
- use crate::lsp::documents::DocumentCache;
+ use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId;
- use crate::lsp::sources::Sources;
use crate::lsp::text::LineIndex;
use std::path::Path;
use std::path::PathBuf;
@@ -2921,37 +2855,19 @@ mod tests {
fixtures: &[(&str, &str, i32, LanguageId)],
location: &Path,
) -> StateSnapshot {
- let mut documents = DocumentCache::default();
+ let documents = Documents::new(location);
for (specifier, source, version, language_id) in fixtures {
let specifier =
resolve_url(specifier).expect("failed to create specifier");
documents.open(
specifier.clone(),
*version,
- *language_id,
+ language_id.clone(),
Arc::new(source.to_string()),
);
- let media_type = MediaType::from(&specifier);
- if let Some(Ok(parsed_module)) =
- documents.get(&specifier).unwrap().source().module()
- {
- let (deps, _) = analysis::analyze_dependencies(
- &specifier,
- media_type,
- parsed_module,
- &None,
- );
- let dep_ranges =
- analysis::analyze_dependency_ranges(parsed_module).ok();
- documents
- .set_dependencies(&specifier, Some(deps), dep_ranges)
- .unwrap();
- }
}
- let sources = Sources::new(location);
StateSnapshot {
documents,
- sources,
..Default::default()
}
}
diff --git a/cli/lsp/urls.rs b/cli/lsp/urls.rs
index 8b47911f6..19f53f297 100644
--- a/cli/lsp/urls.rs
+++ b/cli/lsp/urls.rs
@@ -92,7 +92,9 @@ impl LspUrlMap {
let url = if specifier.scheme() == "file" {
specifier.clone()
} else {
- let specifier_str = if specifier.scheme() == "data" {
+ let specifier_str = if specifier.scheme() == "asset" {
+ format!("deno:asset{}", specifier.path())
+ } else if specifier.scheme() == "data" {
let data_url = DataUrl::process(specifier.as_str())
.map_err(|e| uri_error(format!("{:?}", e)))?;
let mime = data_url.mime_type();
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 3126eb31c..ecfe5d334 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -431,7 +431,7 @@ fn lsp_hover_asset() {
"deno/virtualTextDocument",
json!({
"textDocument": {
- "uri": "deno:/asset//lib.deno.shared_globals.d.ts"
+ "uri": "deno:asset/lib.deno.shared_globals.d.ts"
}
}),
)
@@ -442,7 +442,7 @@ fn lsp_hover_asset() {
"textDocument/hover",
json!({
"textDocument": {
- "uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts"
+ "uri": "deno:asset/lib.es2015.symbol.wellknown.d.ts"
},
"position": {
"line": 109,
@@ -919,11 +919,11 @@ fn lsp_hover_dependency() {
"range": {
"start": {
"line": 0,
- "character": 20
+ "character": 19
},
"end":{
"line": 0,
- "character": 61
+ "character": 62
}
}
}))
@@ -953,11 +953,11 @@ fn lsp_hover_dependency() {
"range": {
"start": {
"line": 3,
- "character": 20
+ "character": 19
},
"end":{
"line": 3,
- "character": 66
+ "character": 67
}
}
}))
@@ -987,11 +987,11 @@ fn lsp_hover_dependency() {
"range": {
"start": {
"line": 4,
- "character": 20
+ "character": 19
},
"end":{
"line": 4,
- "character": 56
+ "character": 57
}
}
}))
@@ -1021,11 +1021,11 @@ fn lsp_hover_dependency() {
"range": {
"start": {
"line": 5,
- "character": 20
+ "character": 19
},
"end":{
"line": 5,
- "character": 131
+ "character": 132
}
}
}))
@@ -1055,11 +1055,11 @@ fn lsp_hover_dependency() {
"range": {
"start": {
"line": 6,
- "character": 20
+ "character": 19
},
"end":{
"line": 6,
- "character": 32
+ "character": 33
}
}
}))
@@ -1771,7 +1771,7 @@ fn lsp_code_lens_non_doc_nav_tree() {
"deno/virtualTextDocument",
json!({
"textDocument": {
- "uri": "deno:/asset//lib.deno.shared_globals.d.ts"
+ "uri": "deno:asset/lib.deno.shared_globals.d.ts"
}
}),
)
@@ -1783,7 +1783,7 @@ fn lsp_code_lens_non_doc_nav_tree() {
"textDocument/codeLens",
json!({
"textDocument": {
- "uri": "deno:/asset//lib.deno.shared_globals.d.ts"
+ "uri": "deno:asset/lib.deno.shared_globals.d.ts"
}
}),
)
@@ -2714,11 +2714,11 @@ fn lsp_cache_location() {
"range": {
"start": {
"line": 0,
- "character": 20
+ "character": 19
},
"end":{
"line": 0,
- "character": 61
+ "character": 62
}
}
}))