0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-10-30 09:08:00 -04:00
denoland-deno/cli/lsp/documents.rs

1152 lines
32 KiB
Rust

// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
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();
}
/// 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,
Tsx,
Json,
JsonC,
Markdown,
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" | "jsx" => Ok(Self::Jsx),
"typescript" => Ok(Self::TypeScript),
"typescriptreact" | "tsx" => Ok(Self::Tsx),
"json" => Ok(Self::Json),
"jsonc" => Ok(Self::JsonC),
"markdown" => Ok(Self::Markdown),
_ => Ok(Self::Unknown),
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum IndexValid {
All,
UpTo(u32),
}
impl IndexValid {
fn covers(&self, line: u32) -> bool {
match *self {
IndexValid::UpTo(to) => to > line,
IndexValid::All => true,
}
}
}
#[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: String,
}
impl Document {
fn new(
specifier: ModuleSpecifier,
version: String,
maybe_headers: Option<&HashMap<String, String>>,
content: Arc<String>,
maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
) -> Self {
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 {
line_index,
maybe_language_id: None,
maybe_lsp_version: None,
maybe_module,
maybe_navigation_tree: None,
maybe_warning,
source,
specifier,
version,
}
}
fn change(
&mut self,
version: i32,
changes: Vec<lsp::TextDocumentContentChangeEvent>,
maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
) -> Result<(), AnyError> {
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 changes {
if let Some(range) = change.range {
if !index_valid.covers(range.start.line) {
line_index = Arc::new(LineIndex::new(&content));
}
index_valid = IndexValid::UpTo(range.start.line);
let range = line_index.get_text_range(range)?;
content.replace_range(Range::<usize>::from(range), &change.text);
} else {
content = change.text;
index_valid = IndexValid::UpTo(0);
}
}
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 {
Arc::new(LineIndex::new(self.source.text_str()))
};
self.maybe_lsp_version = Some(version);
self.maybe_navigation_tree = None;
Ok(())
}
fn close(&mut self) {
self.maybe_lsp_version = None;
self.maybe_language_id = None;
}
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(),
}
}
}
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(),
}
}
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_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(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_map = dependents_map;
}
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,
changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<(), AnyError> {
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()),
)
}
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(())
}
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
}
}
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)
}
fn content(&mut self, specifier: &ModuleSpecifier) -> Option<Arc<String>> {
self.get(specifier).map(|d| d.content())
}
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(),
)
}
fn dependents(
&mut self,
specifier: &ModuleSpecifier,
) -> Vec<ModuleSpecifier> {
if self.dirty {
self.calculate_dependents();
self.dirty = false;
}
let mut dependents = HashSet::new();
if let Some(specifier) = self.resolve_specifier(specifier) {
recurse_dependents(&specifier, &self.dependents_map, &mut dependents);
dependents.into_iter().collect()
} else {
vec![]
}
}
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,
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()))
})
}
fn get_navigation_tree(
&mut self,
specifier: &ModuleSpecifier,
) -> Option<Arc<tsc::NavigationTree>> {
self
.get(specifier)
.map(|d| d.maybe_navigation_tree.clone())
.flatten()
}
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
}
}
fn is_formattable(&mut self, specifier: &ModuleSpecifier) -> bool {
// currently any document that is open in the language server is formattable
self.is_open(specifier)
}
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
}
}
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
}
}
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(|doc| doc.maybe_lsp_version)
.flatten()
}
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,
content: Arc<String>,
) {
let document_data = Document::open(
specifier.clone(),
version,
language_id,
content,
self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
);
self.docs.insert(specifier, document_data);
self.dirty = true;
}
fn parsed_source(
&mut self,
specifier: &ModuleSpecifier,
) -> Option<Result<deno_ast::ParsedSource, deno_graph::ModuleGraphError>> {
self
.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 {
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()
})
.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 resolve_remote_specifier(
&self,
specifier: &ModuleSpecifier,
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
}
}
fn resolve_specifier(
&mut self,
specifier: &ModuleSpecifier,
) -> 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 {
let redirect = self.resolve_remote_specifier(specifier, 10)?;
self.redirects.insert(specifier.clone(), redirect.clone());
Some(redirect)
}
}
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: Arc<tsc::NavigationTree>,
) -> Result<(), AnyError> {
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);
}
/// 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)
}
/// 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 tempfile::TempDir;
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_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(),
);
documents.open(
specifier.clone(),
1,
"javascript".parse().unwrap(),
content,
);
assert!(documents.is_formattable(&specifier));
assert!(documents.is_diagnosable(&specifier));
assert!(documents.line_index(&specifier).is_some());
}
#[test]
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,
"javascript".parse().unwrap(),
content,
);
documents
.change(
&specifier,
2,
vec![lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range {
start: lsp::Position {
line: 1,
character: 13,
},
end: lsp::Position {
line: 1,
character: 13,
},
}),
range_length: None,
text: r#", "hello deno""#.to_string(),
}],
)
.unwrap();
assert_eq!(
documents.content(&specifier).unwrap().as_str(),
r#"import * as b from "./b.ts";
console.log(b, "hello deno");
"#
);
}
}