diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 4979f7febb8e73a2c26a4ea0e6eac085e4f72055..b05a370a6cff8093f556c7c32c174a47a96e8c25 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -20,7 +20,7 @@ use std::{ use util::{ async_maybe, fs::remove_matching, - github::{latest_github_release, GitHubLspBinaryVersion}, + github::{github_release_with_tag, GitHubLspBinaryVersion}, ResultExt, }; @@ -285,13 +285,11 @@ impl LspAdapter for EsLintLspAdapter { &self, delegate: &dyn LspAdapterDelegate, ) -> Result> { - // At the time of writing the latest vscode-eslint release was released in 2020 and requires - // special custom LSP protocol extensions be handled to fully initialize. Download the latest - // prerelease instead to sidestep this issue - let release = latest_github_release( + // We're using this hardcoded release tag, because ESLint's API changed with + // >= 2.3 and we haven't upgraded yet. + let release = github_release_with_tag( "microsoft/vscode-eslint", - false, - true, + "release/2.2.20-Insider", delegate.http_client(), ) .await?; diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index d62856ae601643f88fcb4863092b37188de0f796..7e2578214e6bd7a95bef41ed4136686c296d8f2e 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -3,6 +3,7 @@ use anyhow::{anyhow, bail, Context, Result}; use futures::AsyncReadExt; use serde::Deserialize; use std::sync::Arc; +use url::Url; pub struct GitHubLspBinaryVersion { pub name: String, @@ -74,3 +75,70 @@ pub async fn latest_github_release( .find(|release| release.pre_release == pre_release) .ok_or(anyhow!("Failed to find a release")) } + +pub async fn github_release_with_tag( + repo_name_with_owner: &str, + tag: &str, + http: Arc, +) -> Result { + let url = build_tagged_release_url(repo_name_with_owner, tag)?; + let mut response = http + .get(&url, Default::default(), true) + .await + .context("error fetching latest release")?; + + let mut body = Vec::new(); + response + .body_mut() + .read_to_end(&mut body) + .await + .context("error reading latest release")?; + + if response.status().is_client_error() { + let text = String::from_utf8_lossy(body.as_slice()); + bail!( + "status error {}, response: {text:?}", + response.status().as_u16() + ); + } + + match serde_json::from_slice::(body.as_slice()) { + Ok(release) => Ok(release), + + Err(err) => { + log::error!("Error deserializing: {:?}", err); + log::error!( + "GitHub API response text: {:?}", + String::from_utf8_lossy(body.as_slice()) + ); + return Err(anyhow!("error deserializing latest release")); + } + } +} + +fn build_tagged_release_url(repo_name_with_owner: &str, tag: &str) -> Result { + let mut url = Url::parse(&format!( + "https://api.github.com/repos/{repo_name_with_owner}/releases/tags" + ))?; + // We're pushing this here, because tags may contain `/` and other characters + // that need to be escaped. + url.path_segments_mut() + .map_err(|_| anyhow!("cannot modify url path segments"))? + .push(tag); + Ok(url.to_string()) +} + +#[cfg(test)] +mod tests { + use super::build_tagged_release_url; + + #[test] + fn test_build_tagged_release_url() { + let tag = "release/2.2.20-Insider"; + let repo_name_with_owner = "microsoft/vscode-eslint"; + + let have = build_tagged_release_url(repo_name_with_owner, tag).unwrap(); + + assert_eq!(have, "https://api.github.com/repos/microsoft/vscode-eslint/releases/tags/release%2F2.2.20-Insider"); + } +}