summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2024-02-10 01:27:04 +0000
committerGitHub <noreply@github.com>2024-02-10 01:27:04 +0000
commit34c8d1714026501a0d4907b7311006fadb25fcdf (patch)
treef5b6148aae2539c89fbad4b34ebe558dee9df95b
parentf232c36c485d2b4f15b52c32005e33a965424eee (diff)
fix(lsp): complete npm specifier versions correctly (#22332)
-rw-r--r--cli/lsp/completions.rs76
1 files changed, 55 insertions, 21 deletions
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index a1f669197..7fe161039 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -474,38 +474,40 @@ fn get_relative_specifiers(
.collect()
}
-/// Get completions for `npm:` specifiers.
-async fn get_npm_completions(
- referrer: &ModuleSpecifier,
- specifier: &str,
- range: &lsp::Range,
- npm_search_api: &impl NpmSearchApi,
-) -> Option<Vec<lsp::CompletionItem>> {
- debug_assert!(specifier.starts_with("npm:"));
- let bare_specifier = &specifier[4..];
-
- // Find the index of the '@' delimiting the package name and version, if any.
- let v_index = if bare_specifier.starts_with('@') {
+/// Find the index of the '@' delimiting the package name and version, if any.
+fn parse_npm_specifier_version_index(specifier: &str) -> Option<usize> {
+ let bare_specifier = specifier.strip_prefix("npm:")?;
+ if bare_specifier.starts_with('@') {
bare_specifier
.find('/')
.filter(|idx| !bare_specifier[1..*idx].is_empty())
.and_then(|idx| {
bare_specifier[idx..]
.find('@')
- .filter(|idx2| !bare_specifier[(idx + 1)..*idx2].is_empty())
- .filter(|idx2| !bare_specifier[(idx + 1)..*idx2].contains('/'))
+ .filter(|idx2| !bare_specifier[idx..][1..*idx2].is_empty())
+ .filter(|idx2| !bare_specifier[idx..][1..*idx2].contains('/'))
+ .map(|idx2| 4 + idx + idx2)
})
} else {
bare_specifier
.find('@')
- .filter(|idx| !bare_specifier[..*idx].is_empty())
- .filter(|idx| !bare_specifier[..*idx].contains('/'))
- };
+ .filter(|idx| !bare_specifier[1..*idx].is_empty())
+ .filter(|idx| !bare_specifier[1..*idx].contains('/'))
+ .map(|idx| 4 + idx)
+ }
+}
+/// Get completions for `npm:` specifiers.
+async fn get_npm_completions(
+ referrer: &ModuleSpecifier,
+ specifier: &str,
+ range: &lsp::Range,
+ npm_search_api: &impl NpmSearchApi,
+) -> Option<Vec<lsp::CompletionItem>> {
// First try to match `npm:some-package@<version-to-complete>`.
- if let Some(v_index) = v_index {
- let package_name = &bare_specifier[..v_index];
- let v_prefix = &bare_specifier[(v_index + 1)..];
+ if let Some(v_index) = parse_npm_specifier_version_index(specifier) {
+ let package_name = &specifier[..v_index].strip_prefix("npm:")?;
+ let v_prefix = &specifier[(v_index + 1)..];
let versions = &npm_search_api
.package_info(package_name)
.await
@@ -550,7 +552,8 @@ async fn get_npm_completions(
}
// Otherwise match `npm:<package-to-complete>`.
- let names = npm_search_api.search(bare_specifier).await.ok()?;
+ let package_name_prefix = specifier.strip_prefix("npm:")?;
+ let names = npm_search_api.search(package_name_prefix).await.ok()?;
let items = names
.iter()
.enumerate()
@@ -842,6 +845,37 @@ mod tests {
);
}
+ #[test]
+ fn test_parse_npm_specifier_version_index() {
+ assert_eq!(parse_npm_specifier_version_index("npm:"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:/"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:/@"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:@"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:@/"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:@/@"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:foo"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:foo/bar"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:foo/bar@"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:@org/foo/bar"), None);
+ assert_eq!(parse_npm_specifier_version_index("npm:@org/foo/bar@"), None);
+
+ assert_eq!(parse_npm_specifier_version_index("npm:foo@"), Some(7));
+ assert_eq!(parse_npm_specifier_version_index("npm:foo@1."), Some(7));
+ assert_eq!(parse_npm_specifier_version_index("npm:@org/foo@"), Some(12));
+ assert_eq!(
+ parse_npm_specifier_version_index("npm:@org/foo@1."),
+ Some(12)
+ );
+
+ // Regression test for https://github.com/denoland/deno/issues/22325.
+ assert_eq!(
+ parse_npm_specifier_version_index(
+ "npm:@longer_than_right_one/arbitrary_string@"
+ ),
+ Some(43)
+ );
+ }
+
#[tokio::test]
async fn test_get_npm_completions() {
let npm_search_api = TestNpmSearchApi(