1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

refactor(lsp): consolidate relative_specifier (#16780)

Closes #14840
This commit is contained in:
David Sherret 2022-11-25 09:16:56 -05:00 committed by GitHub
parent 3f79c9b6e5
commit d80af8324d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 206 deletions

View file

@ -1053,34 +1053,92 @@ mod tests {
#[test]
fn test_relative_specifier() {
run_test("file:///from", "file:///to", Some("./to"));
run_test("file:///from", "file:///from/other", Some("./from/other"));
run_test("file:///from", "file:///from/other/", Some("./from/other/"));
run_test("file:///from", "file:///other/from", Some("./other/from"));
run_test("file:///from/", "file:///other/from", Some("../other/from"));
run_test("file:///from", "file:///other/from/", Some("./other/from/"));
run_test(
"file:///from",
"file:///to/other.txt",
Some("./to/other.txt"),
);
run_test(
"file:///from/test",
"file:///to/other.txt",
Some("../to/other.txt"),
);
run_test(
"file:///from/other.txt",
"file:///to/other.txt",
Some("../to/other.txt"),
);
fn run_test(from: &str, to: &str, expected: Option<&str>) {
let result = relative_specifier(
&ModuleSpecifier::parse(from).unwrap(),
&ModuleSpecifier::parse(to).unwrap(),
let fixtures: Vec<(&str, &str, Option<&str>)> = vec![
("file:///from", "file:///to", Some("./to")),
("file:///from", "file:///from/other", Some("./from/other")),
("file:///from", "file:///from/other/", Some("./from/other/")),
("file:///from", "file:///other/from", Some("./other/from")),
("file:///from/", "file:///other/from", Some("../other/from")),
("file:///from", "file:///other/from/", Some("./other/from/")),
(
"file:///from",
"file:///to/other.txt",
Some("./to/other.txt"),
),
(
"file:///from/test",
"file:///to/other.txt",
Some("../to/other.txt"),
),
(
"file:///from/other.txt",
"file:///to/other.txt",
Some("../to/other.txt"),
),
(
"https://deno.land/x/a/b/d.ts",
"https://deno.land/x/a/b/c.ts",
Some("./c.ts"),
),
(
"https://deno.land/x/a/b/d.ts",
"https://deno.land/x/a/c.ts",
Some("../c.ts"),
),
(
"https://deno.land/x/a/b/d.ts",
"https://deno.land/x/a/b/c/d.ts",
Some("./c/d.ts"),
),
(
"https://deno.land/x/a/b/c/",
"https://deno.land/x/a/b/c/d.ts",
Some("./d.ts"),
),
(
"https://deno.land/x/a/b/c/",
"https://deno.land/x/a/b/c/d/e.ts",
Some("./d/e.ts"),
),
(
"https://deno.land/x/a/b/c/f.ts",
"https://deno.land/x/a/b/c/d/e.ts",
Some("./d/e.ts"),
),
(
"https://deno.land/x/a/b/d.ts",
"https://deno.land/x/a/c.ts?foo=bar",
Some("../c.ts?foo=bar"),
),
(
"https://deno.land/x/a/b/d.ts?foo=bar",
"https://deno.land/x/a/b/c.ts",
Some("./c.ts"),
),
("file:///a/b/d.ts", "file:///a/b/c.ts", Some("./c.ts")),
("https://deno.land/x/a/b/c.ts", "file:///a/b/c.ts", None),
(
"https://deno.land/",
"https://deno.land/x/a/b/c.ts",
Some("./x/a/b/c.ts"),
),
(
"https://deno.land/x/d/e/f.ts",
"https://deno.land/x/a/b/c.ts",
Some("../../a/b/c.ts"),
),
];
for (from_str, to_str, expected) in fixtures {
let from = ModuleSpecifier::parse(from_str).unwrap();
let to = ModuleSpecifier::parse(to_str).unwrap();
let actual = relative_specifier(&from, &to);
assert_eq!(
actual.as_deref(),
expected,
"from: \"{}\" to: \"{}\"",
from_str,
to_str
);
assert_eq!(result.as_deref(), expected);
}
}

View file

@ -8,6 +8,7 @@ use super::registries::ModuleRegistry;
use super::tsc;
use crate::fs_util::is_supported_ext;
use crate::fs_util::relative_specifier;
use crate::fs_util::specifier_to_file_path;
use deno_ast::LineAndColumnIndex;
@ -378,7 +379,7 @@ fn get_local_completions(
if &entry_specifier == base {
return None;
}
let full_text = relative_specifier(&entry_specifier, base);
let full_text = relative_specifier(base, &entry_specifier)?;
// this weeds out situations where we are browsing in the parent, but
// we want to filter out non-matches when the completion is manually
// invoked by the user, but still allows for things like `../src/../`
@ -443,7 +444,7 @@ fn get_relative_specifiers(
.iter()
.filter_map(|s| {
if s != base {
Some(relative_specifier(s, base))
Some(relative_specifier(base, s).unwrap_or_else(|| s.to_string()))
} else {
None
}
@ -501,103 +502,6 @@ fn get_workspace_completions(
.collect()
}
/// Converts a specifier into a relative specifier to the provided base
/// specifier as a string. If a relative path cannot be found, then the
/// specifier is simply returned as a string.
///
/// ```
/// use deno_core::resolve_url;
///
/// let specifier = resolve_url("file:///a/b.ts").unwrap();
/// let base = resolve_url("file:///a/c/d.ts").unwrap();
/// assert_eq!(relative_specifier(&specifier, &base), "../b.ts");
/// ```
///
pub fn relative_specifier(
specifier: &ModuleSpecifier,
base: &ModuleSpecifier,
) -> String {
if specifier.cannot_be_a_base()
|| base.cannot_be_a_base()
|| specifier.scheme() != base.scheme()
|| specifier.host() != base.host()
|| specifier.port_or_known_default() != base.port_or_known_default()
{
if specifier.scheme() == "file" {
specifier_to_file_path(specifier)
.unwrap()
.to_string_lossy()
.into()
} else {
specifier.as_str().into()
}
} else if let (Some(iter_a), Some(iter_b)) =
(specifier.path_segments(), base.path_segments())
{
let mut vec_a: Vec<&str> = iter_a.collect();
let mut vec_b: Vec<&str> = iter_b.collect();
let last_a = if !specifier.path().ends_with('/') && !vec_a.is_empty() {
vec_a.pop().unwrap()
} else {
""
};
let is_dir_b = base.path().ends_with('/');
if !is_dir_b && !vec_b.is_empty() {
vec_b.pop();
}
if !vec_a.is_empty() && !vec_b.is_empty() && base.path() != "/" {
let mut parts: Vec<&str> = Vec::new();
let mut segments_a = vec_a.into_iter();
let mut segments_b = vec_b.into_iter();
loop {
match (segments_a.next(), segments_b.next()) {
(None, None) => break,
(Some(a), None) => {
if parts.is_empty() {
parts.push(CURRENT_PATH);
}
parts.push(a);
parts.extend(segments_a.by_ref());
break;
}
(None, _) if is_dir_b => parts.push(CURRENT_PATH),
(None, _) => parts.push(PARENT_PATH),
(Some(a), Some(b)) if parts.is_empty() && a == b => (),
(Some(a), Some(b)) if b == CURRENT_PATH => parts.push(a),
(Some(_), Some(b)) if b == PARENT_PATH => {
return specifier[Position::BeforePath..].to_string()
}
(Some(a), Some(_)) => {
if parts.is_empty() && is_dir_b {
parts.push(CURRENT_PATH);
} else {
parts.push(PARENT_PATH);
}
// actually the clippy suggestions here are less readable for once
#[allow(clippy::same_item_push)]
for _ in segments_b {
parts.push(PARENT_PATH);
}
parts.push(a);
parts.extend(segments_a.by_ref());
break;
}
}
}
if parts.is_empty() {
format!("./{}{}", last_a, &specifier[Position::AfterPath..])
} else {
parts.push(last_a);
format!("{}{}", parts.join("/"), &specifier[Position::AfterPath..])
}
} else {
specifier[Position::BeforePath..].into()
}
} else {
specifier[Position::BeforePath..].into()
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -671,80 +575,6 @@ mod tests {
);
}
#[test]
fn test_relative_specifier() {
let fixtures: Vec<(&str, &str, &str)> = vec![
(
"https://deno.land/x/a/b/c.ts",
"https://deno.land/x/a/b/d.ts",
"./c.ts",
),
(
"https://deno.land/x/a/c.ts",
"https://deno.land/x/a/b/d.ts",
"../c.ts",
),
(
"https://deno.land/x/a/b/c/d.ts",
"https://deno.land/x/a/b/d.ts",
"./c/d.ts",
),
(
"https://deno.land/x/a/b/c/d.ts",
"https://deno.land/x/a/b/c/",
"./d.ts",
),
(
"https://deno.land/x/a/b/c/d/e.ts",
"https://deno.land/x/a/b/c/",
"./d/e.ts",
),
(
"https://deno.land/x/a/b/c/d/e.ts",
"https://deno.land/x/a/b/c/f.ts",
"./d/e.ts",
),
(
"https://deno.land/x/a/c.ts?foo=bar",
"https://deno.land/x/a/b/d.ts",
"../c.ts?foo=bar",
),
(
"https://deno.land/x/a/b/c.ts",
"https://deno.land/x/a/b/d.ts?foo=bar",
"./c.ts",
),
#[cfg(not(windows))]
("file:///a/b/c.ts", "file:///a/b/d.ts", "./c.ts"),
#[cfg(not(windows))]
(
"file:///a/b/c.ts",
"https://deno.land/x/a/b/c.ts",
"/a/b/c.ts",
),
(
"https://deno.land/x/a/b/c.ts",
"https://deno.land/",
"/x/a/b/c.ts",
),
(
"https://deno.land/x/a/b/c.ts",
"https://deno.land/x/d/e/f.ts",
"../../a/b/c.ts",
),
];
for (specifier_str, base_str, expected) in fixtures {
let specifier = resolve_url(specifier_str).unwrap();
let base = resolve_url(base_str).unwrap();
let actual = relative_specifier(&specifier, &base);
assert_eq!(
actual, expected,
"specifier: \"{}\" base: \"{}\"",
specifier_str, base_str
);
}
}
#[test]
fn test_get_local_completions() {
let temp_dir = TempDir::new();

View file

@ -1,7 +1,6 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use super::code_lens;
use super::completions::relative_specifier;
use super::config;
use super::documents::AssetOrDocument;
use super::language_server;
@ -19,6 +18,7 @@ use super::urls::LspUrlMap;
use super::urls::INVALID_SPECIFIER;
use crate::args::TsConfig;
use crate::fs_util::relative_specifier;
use crate::fs_util::specifier_to_file_path;
use crate::tsc;
use crate::tsc::ResolveArgs;
@ -2098,11 +2098,13 @@ fn update_import_statement(
{
if let Ok(import_specifier) = normalize_specifier(&import_data.file_name)
{
let new_module_specifier =
relative_specifier(&import_specifier, &item_data.specifier);
text_edit.new_text = text_edit
.new_text
.replace(&import_data.module_specifier, &new_module_specifier);
if let Some(new_module_specifier) =
relative_specifier(&item_data.specifier, &import_specifier)
{
text_edit.new_text = text_edit
.new_text
.replace(&import_data.module_specifier, &new_module_specifier);
}
}
}
}