diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-10-29 10:56:01 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-29 10:56:01 +1100 |
commit | 34a9ddff091950aee9d89915cd13944259e7d346 (patch) | |
tree | 9ccf8c07c34fe5661c90097584eb8b47da3e9ba9 /cli/lsp/analysis.rs | |
parent | 74a93fdf63a17de990954399b10eb6dfe7dd1973 (diff) |
refactor(lsp): use deno_graph and single document struct (#12535)
Closes #12473
Diffstat (limited to 'cli/lsp/analysis.rs')
-rw-r--r-- | cli/lsp/analysis.rs | 727 |
1 files changed, 16 insertions, 711 deletions
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: ®ex::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!( - "{}​{}", - 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(), - }) - ); - } } |