languages: Pin TypeScript to 6.x for typescript-language-server (#60970)

Closes https://github.com/zed-industries/zed/issues/60618

TypeScript 7 no longer ships `tsserver.js`, which
`typescript-language-server` depends on.

This PR does two things:
1. Pins the `typescript` package Zed auto-installs for
`typescript-language-server` to `^6`. For projects without their own
TypeScript in `node_modules` (which rely on the copy Zed installs), an
existing 7.x install gets downgraded to the latest 6.x, making the
server usable again.
2. Only hands the workspace's TypeScript to the server as a tsdk when it
actually contains `tsserver.js`, falling back to Zed's installed 6.x
otherwise. So if you upgraded your project to TypeScript 7, the language
server won't break. It keeps working against the 6.x TypeScript Zed
ships.

Note that vtsls, the default TypeScript language server, is unaffected
by the TypeScript 7 release. It bundles its own compatible TypeScript.
See #60951 for details. This PR only affects users who have manually
configured `typescript-language-server` as their language server.

Release Notes:

- Fixed `typescript-language-server` breaking after the TypeScript 7
release. For projects that have upgraded to TypeScript 7, install the
`tsgo` extension to use TypeScript's native language server.
This commit is contained in:
Smit Barmase authored and GitHub committed 2026-07-14 13:21:41 +00:00
1 parent e46729d1d8
commit 440affd443
2 files changed
+201 -22

No files matched your search

+178 -15
View File
@@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
use futures::{AsyncReadExt, FutureExt as _, channel::oneshot, future::Shared};
use http_client::{Host, HttpClient, Url};
use log::Level;
use semver::Version;
use semver::{Version, VersionReq};
use serde::Deserialize;
use smol::io::BufReader;
use smol::{fs, lock::Mutex};
@@ -253,6 +253,15 @@ impl NodeRuntime {
}
pub async fn npm_package_latest_version(&self, name: &str) -> Result<Version> {
self.npm_package_latest_version_with_requirement(name, None)
.await
}
pub async fn npm_package_latest_version_with_requirement(
&self,
name: &str,
version_requirement: Option<&VersionReq>,
) -> Result<Version> {
let http = self.0.lock().await.http.clone();
let instance = self.instance().await;
let output = instance
@@ -285,9 +294,10 @@ impl NodeRuntime {
.log_err()
.flatten();
let latest_dist_tag = info.dist_tags.latest.clone();
let selected_version = select_npm_package_version(name, info, before.as_deref())?;
let selected_version =
select_npm_package_version(name, info, before.as_deref(), version_requirement)?;
log::debug!(
"selected latest npm package version package={name:?} before={before:?} dist_tag_latest={latest_dist_tag:?} selected={selected_version}"
"selected latest npm package version package={name:?} version_requirement={version_requirement:?} before={before:?} dist_tag_latest={latest_dist_tag:?} selected={selected_version}"
);
Ok(selected_version)
}
@@ -481,7 +491,19 @@ fn select_npm_package_version(
package_name: &str,
mut info: NpmInfo,
before: Option<&str>,
version_requirement: Option<&VersionReq>,
) -> Result<Version> {
if let Some(version_requirement) = version_requirement {
info.versions
.retain(|version| version_requirement.matches(version));
info.versions.sort();
info.dist_tags.latest = info
.dist_tags
.latest
.take()
.filter(|version| version_requirement.matches(version));
}
if let Some(before) = before
&& !info.time.is_empty()
{
@@ -502,6 +524,7 @@ fn select_npm_package_version(
latest_version,
&info.time,
&before_timestamp,
version_requirement.is_some(),
)? {
return Ok(version.clone());
}
@@ -521,8 +544,9 @@ fn is_allowed_npm_version_before(
latest_version: Option<&Version>,
published_at_by_version: &HashMap<String, String>,
before: &DateTime<Utc>,
allow_prereleases: bool,
) -> Result<bool> {
if !version.pre.is_empty()
if (!allow_prereleases && !version.pre.is_empty())
|| latest_version.is_some_and(|latest_version| version > latest_version)
{
return Ok(false);
@@ -1121,7 +1145,7 @@ mod tests {
use anyhow::{Result, bail};
use http_client::Url;
use semver::Version;
use semver::{Version, VersionReq};
use super::{
NpmInfo, VersionStrategy, build_npm_command_args, deserialize_npm_info_from_response,
@@ -1245,7 +1269,7 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, None)?,
select_npm_package_version("test-package", info, None, None)?,
Version::parse("3.0.0")?
);
Ok(())
@@ -1270,7 +1294,12 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
None
)?,
Version::parse("2.0.0")?
);
Ok(())
@@ -1291,7 +1320,12 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
None
)?,
Version::parse("2.0.0")?
);
Ok(())
@@ -1312,7 +1346,12 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
None
)?,
Version::parse("2.0.0")?
);
Ok(())
@@ -1332,7 +1371,12 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
None
)?,
Version::parse("2.0.0-beta.1")?
);
Ok(())
@@ -1353,7 +1397,12 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
None
)?,
Version::parse("1.0.0")?
);
Ok(())
@@ -1374,7 +1423,12 @@ mod tests {
)?;
assert_eq!(
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
None
)?,
Version::parse("1.0.0")?
);
Ok(())
@@ -1393,9 +1447,12 @@ mod tests {
}"#,
)?;
let Err(error) =
select_npm_package_version("test-package", info, Some("2023-12-01T00:00:00.000Z"))
else {
let Err(error) = select_npm_package_version(
"test-package",
info,
Some("2023-12-01T00:00:00.000Z"),
None,
) else {
bail!("expected cutoff to reject all package versions");
};
assert_eq!(
@@ -1405,6 +1462,112 @@ mod tests {
Ok(())
}
#[test]
fn test_select_npm_package_version_selects_latest_matching_requirement() -> Result<()> {
let info: NpmInfo = serde_json::from_str(
r#"{
"dist-tags": { "latest": "7.0.0" },
"versions": ["6.0.3", "7.0.0", "5.9.3", "6.0.2"]
}"#,
)?;
let version_requirement = VersionReq::parse("^6")?;
assert_eq!(
select_npm_package_version("test-package", info, None, Some(&version_requirement))?,
Version::parse("6.0.3")?
);
Ok(())
}
#[test]
fn test_select_npm_package_version_applies_before_to_matching_versions() -> Result<()> {
let info: NpmInfo = serde_json::from_str(
r#"{
"dist-tags": { "latest": "7.0.0" },
"versions": ["6.0.3", "7.0.0", "6.0.2"],
"time": {
"6.0.2": "2024-02-01T00:00:00.000Z",
"6.0.3": "2024-03-01T00:00:00.000Z",
"7.0.0": "2024-04-01T00:00:00.000Z"
}
}"#,
)?;
let version_requirement = VersionReq::parse("^6")?;
assert_eq!(
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
Some(&version_requirement),
)?,
Version::parse("6.0.2")?
);
Ok(())
}
#[test]
fn test_select_npm_package_version_allows_requested_prerelease_before_cutoff() -> Result<()> {
let info: NpmInfo = serde_json::from_str(
r#"{
"dist-tags": { "latest": "7.0.0" },
"versions": ["7.1.0-beta.1", "7.1.0-beta.2", "7.0.0"],
"time": {
"7.0.0": "2024-01-01T00:00:00.000Z",
"7.1.0-beta.1": "2024-02-01T00:00:00.000Z",
"7.1.0-beta.2": "2024-03-01T00:00:00.000Z"
}
}"#,
)?;
let version_requirement = VersionReq::parse(">=7.1.0-beta.1, <7.1.0")?;
assert_eq!(
select_npm_package_version(
"test-package",
info,
Some("2024-02-15T00:00:00.000Z"),
Some(&version_requirement),
)?,
Version::parse("7.1.0-beta.1")?
);
Ok(())
}
#[test]
fn test_select_npm_package_version_errors_without_matching_version() -> Result<()> {
let info: NpmInfo = serde_json::from_str(
r#"{
"dist-tags": { "latest": "7.0.0" },
"versions": ["5.9.3", "7.0.0"]
}"#,
)?;
let version_requirement = VersionReq::parse("^6")?;
let error =
select_npm_package_version("test-package", info, None, Some(&version_requirement))
.expect_err("expected version requirement to reject all package versions");
assert_eq!(
error.to_string(),
"no version found for npm package test-package"
);
Ok(())
}
#[test]
fn test_pinned_version_strategy_replaces_different_installed_version() -> Result<()> {
let pinned_version = Version::parse("6.0.3")?;
assert!(!should_install_npm_package_version(
&pinned_version,
VersionStrategy::Pin(&pinned_version)
));
assert!(should_install_npm_package_version(
&Version::parse("7.0.0")?,
VersionStrategy::Pin(&pinned_version)
));
Ok(())
}
#[test]
fn test_deserialize_npm_info_npm11_format() -> Result<()> {
let json = r#"{