Skip to content

Commit a25f19c

Browse files
authored
languages: Pin TypeScript to 6.x for typescript-language-server (#60970)
Closes #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.
1 parent e06d4b2 commit a25f19c

2 files changed

Lines changed: 201 additions & 22 deletions

File tree

crates/languages/src/typescript.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use language::{
1212
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName, Uri};
1313
use node_runtime::{NodeRuntime, VersionStrategy};
1414
use project::{Fs, lsp_store::language_server_settings};
15-
use semver::Version;
15+
use semver::{Version, VersionReq};
1616
use serde_json::{Value, json};
1717
use smol::lock::RwLock;
1818
use std::{
@@ -601,6 +601,9 @@ fn replace_test_name_parameters(test_name: &str) -> String {
601601
PATTERN.split(test_name).map(regex::escape).join("(.+?)")
602602
}
603603

604+
static TYPESCRIPT_VERSION_REQ: LazyLock<VersionReq> =
605+
LazyLock::new(|| VersionReq::parse("^6").expect("Failed to parse TypeScript version req"));
606+
604607
pub struct TypeScriptLspAdapter {
605608
fs: Arc<dyn Fs>,
606609
node: NodeRuntime,
@@ -634,7 +637,12 @@ impl TypeScriptLspAdapter {
634637

635638
if self
636639
.fs
637-
.is_dir(&adapter.worktree_root_path().join(tsdk_path))
640+
.is_file(
641+
&adapter
642+
.worktree_root_path()
643+
.join(tsdk_path)
644+
.join("tsserver.js"),
645+
)
638646
.await
639647
{
640648
Some(tsdk_path)
@@ -661,7 +669,10 @@ impl LspInstaller for TypeScriptLspAdapter {
661669
Ok(TypeScriptVersions {
662670
typescript_version: self
663671
.node
664-
.npm_package_latest_version(Self::PACKAGE_NAME)
672+
.npm_package_latest_version_with_requirement(
673+
Self::PACKAGE_NAME,
674+
Some(&TYPESCRIPT_VERSION_REQ),
675+
)
665676
.await?,
666677
server_version: self
667678
.node
@@ -684,12 +695,13 @@ impl LspInstaller for TypeScriptLspAdapter {
684695
async move {
685696
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
686697

698+
// Pin rather than Latest so an unusable TypeScript 7.x install gets downgraded.
687699
if node
688700
.should_install_npm_package(
689701
Self::PACKAGE_NAME,
690702
&server_path,
691703
&container_dir,
692-
VersionStrategy::Latest(&typescript_version),
704+
VersionStrategy::Pin(&typescript_version),
693705
)
694706
.await
695707
{
@@ -718,18 +730,22 @@ impl LspInstaller for TypeScriptLspAdapter {
718730

719731
fn fetch_server_binary(
720732
&self,
721-
_latest_version: Self::BinaryVersion,
733+
latest_version: Self::BinaryVersion,
722734
container_dir: PathBuf,
723735
_: &Arc<dyn LspAdapterDelegate>,
724736
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
725737
let node = self.node.clone();
726738

727739
async move {
728740
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
741+
let typescript_version = latest_version.typescript_version.to_string();
729742

730-
node.npm_install_latest_packages(
743+
node.npm_install_packages(
731744
&container_dir,
732-
&[Self::PACKAGE_NAME, Self::SERVER_PACKAGE_NAME],
745+
&[
746+
(Self::PACKAGE_NAME, typescript_version.as_str()),
747+
(Self::SERVER_PACKAGE_NAME, "latest"),
748+
],
733749
)
734750
.await?;
735751

crates/node_runtime/src/node_runtime.rs

Lines changed: 178 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
55
use futures::{AsyncReadExt, FutureExt as _, channel::oneshot, future::Shared};
66
use http_client::{Host, HttpClient, Url};
77
use log::Level;
8-
use semver::Version;
8+
use semver::{Version, VersionReq};
99
use serde::Deserialize;
1010
use smol::io::BufReader;
1111
use smol::{fs, lock::Mutex};
@@ -253,6 +253,15 @@ impl NodeRuntime {
253253
}
254254

255255
pub async fn npm_package_latest_version(&self, name: &str) -> Result<Version> {
256+
self.npm_package_latest_version_with_requirement(name, None)
257+
.await
258+
}
259+
260+
pub async fn npm_package_latest_version_with_requirement(
261+
&self,
262+
name: &str,
263+
version_requirement: Option<&VersionReq>,
264+
) -> Result<Version> {
256265
let http = self.0.lock().await.http.clone();
257266
let instance = self.instance().await;
258267
let output = instance
@@ -285,9 +294,10 @@ impl NodeRuntime {
285294
.log_err()
286295
.flatten();
287296
let latest_dist_tag = info.dist_tags.latest.clone();
288-
let selected_version = select_npm_package_version(name, info, before.as_deref())?;
297+
let selected_version =
298+
select_npm_package_version(name, info, before.as_deref(), version_requirement)?;
289299
log::debug!(
290-
"selected latest npm package version package={name:?} before={before:?} dist_tag_latest={latest_dist_tag:?} selected={selected_version}"
300+
"selected latest npm package version package={name:?} version_requirement={version_requirement:?} before={before:?} dist_tag_latest={latest_dist_tag:?} selected={selected_version}"
291301
);
292302
Ok(selected_version)
293303
}
@@ -481,7 +491,19 @@ fn select_npm_package_version(
481491
package_name: &str,
482492
mut info: NpmInfo,
483493
before: Option<&str>,
494+
version_requirement: Option<&VersionReq>,
484495
) -> Result<Version> {
496+
if let Some(version_requirement) = version_requirement {
497+
info.versions
498+
.retain(|version| version_requirement.matches(version));
499+
info.versions.sort();
500+
info.dist_tags.latest = info
501+
.dist_tags
502+
.latest
503+
.take()
504+
.filter(|version| version_requirement.matches(version));
505+
}
506+
485507
if let Some(before) = before
486508
&& !info.time.is_empty()
487509
{
@@ -502,6 +524,7 @@ fn select_npm_package_version(
502524
latest_version,
503525
&info.time,
504526
&before_timestamp,
527+
version_requirement.is_some(),
505528
)? {
506529
return Ok(version.clone());
507530
}
@@ -521,8 +544,9 @@ fn is_allowed_npm_version_before(
521544
latest_version: Option<&Version>,
522545
published_at_by_version: &HashMap<String, String>,
523546
before: &DateTime<Utc>,
547+
allow_prereleases: bool,
524548
) -> Result<bool> {
525-
if !version.pre.is_empty()
549+
if (!allow_prereleases && !version.pre.is_empty())
526550
|| latest_version.is_some_and(|latest_version| version > latest_version)
527551
{
528552
return Ok(false);
@@ -1121,7 +1145,7 @@ mod tests {
11211145

11221146
use anyhow::{Result, bail};
11231147
use http_client::Url;
1124-
use semver::Version;
1148+
use semver::{Version, VersionReq};
11251149

11261150
use super::{
11271151
NpmInfo, VersionStrategy, build_npm_command_args, deserialize_npm_info_from_response,
@@ -1245,7 +1269,7 @@ mod tests {
12451269
)?;
12461270

12471271
assert_eq!(
1248-
select_npm_package_version("test-package", info, None)?,
1272+
select_npm_package_version("test-package", info, None, None)?,
12491273
Version::parse("3.0.0")?
12501274
);
12511275
Ok(())
@@ -1270,7 +1294,12 @@ mod tests {
12701294
)?;
12711295

12721296
assert_eq!(
1273-
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
1297+
select_npm_package_version(
1298+
"test-package",
1299+
info,
1300+
Some("2024-02-15T00:00:00.000Z"),
1301+
None
1302+
)?,
12741303
Version::parse("2.0.0")?
12751304
);
12761305
Ok(())
@@ -1291,7 +1320,12 @@ mod tests {
12911320
)?;
12921321

12931322
assert_eq!(
1294-
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
1323+
select_npm_package_version(
1324+
"test-package",
1325+
info,
1326+
Some("2024-02-15T00:00:00.000Z"),
1327+
None
1328+
)?,
12951329
Version::parse("2.0.0")?
12961330
);
12971331
Ok(())
@@ -1312,7 +1346,12 @@ mod tests {
13121346
)?;
13131347

13141348
assert_eq!(
1315-
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
1349+
select_npm_package_version(
1350+
"test-package",
1351+
info,
1352+
Some("2024-02-15T00:00:00.000Z"),
1353+
None
1354+
)?,
13161355
Version::parse("2.0.0")?
13171356
);
13181357
Ok(())
@@ -1332,7 +1371,12 @@ mod tests {
13321371
)?;
13331372

13341373
assert_eq!(
1335-
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
1374+
select_npm_package_version(
1375+
"test-package",
1376+
info,
1377+
Some("2024-02-15T00:00:00.000Z"),
1378+
None
1379+
)?,
13361380
Version::parse("2.0.0-beta.1")?
13371381
);
13381382
Ok(())
@@ -1353,7 +1397,12 @@ mod tests {
13531397
)?;
13541398

13551399
assert_eq!(
1356-
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
1400+
select_npm_package_version(
1401+
"test-package",
1402+
info,
1403+
Some("2024-02-15T00:00:00.000Z"),
1404+
None
1405+
)?,
13571406
Version::parse("1.0.0")?
13581407
);
13591408
Ok(())
@@ -1374,7 +1423,12 @@ mod tests {
13741423
)?;
13751424

13761425
assert_eq!(
1377-
select_npm_package_version("test-package", info, Some("2024-02-15T00:00:00.000Z"))?,
1426+
select_npm_package_version(
1427+
"test-package",
1428+
info,
1429+
Some("2024-02-15T00:00:00.000Z"),
1430+
None
1431+
)?,
13781432
Version::parse("1.0.0")?
13791433
);
13801434
Ok(())
@@ -1393,9 +1447,12 @@ mod tests {
13931447
}"#,
13941448
)?;
13951449

1396-
let Err(error) =
1397-
select_npm_package_version("test-package", info, Some("2023-12-01T00:00:00.000Z"))
1398-
else {
1450+
let Err(error) = select_npm_package_version(
1451+
"test-package",
1452+
info,
1453+
Some("2023-12-01T00:00:00.000Z"),
1454+
None,
1455+
) else {
13991456
bail!("expected cutoff to reject all package versions");
14001457
};
14011458
assert_eq!(
@@ -1405,6 +1462,112 @@ mod tests {
14051462
Ok(())
14061463
}
14071464

1465+
#[test]
1466+
fn test_select_npm_package_version_selects_latest_matching_requirement() -> Result<()> {
1467+
let info: NpmInfo = serde_json::from_str(
1468+
r#"{
1469+
"dist-tags": { "latest": "7.0.0" },
1470+
"versions": ["6.0.3", "7.0.0", "5.9.3", "6.0.2"]
1471+
}"#,
1472+
)?;
1473+
let version_requirement = VersionReq::parse("^6")?;
1474+
1475+
assert_eq!(
1476+
select_npm_package_version("test-package", info, None, Some(&version_requirement))?,
1477+
Version::parse("6.0.3")?
1478+
);
1479+
Ok(())
1480+
}
1481+
1482+
#[test]
1483+
fn test_select_npm_package_version_applies_before_to_matching_versions() -> Result<()> {
1484+
let info: NpmInfo = serde_json::from_str(
1485+
r#"{
1486+
"dist-tags": { "latest": "7.0.0" },
1487+
"versions": ["6.0.3", "7.0.0", "6.0.2"],
1488+
"time": {
1489+
"6.0.2": "2024-02-01T00:00:00.000Z",
1490+
"6.0.3": "2024-03-01T00:00:00.000Z",
1491+
"7.0.0": "2024-04-01T00:00:00.000Z"
1492+
}
1493+
}"#,
1494+
)?;
1495+
let version_requirement = VersionReq::parse("^6")?;
1496+
1497+
assert_eq!(
1498+
select_npm_package_version(
1499+
"test-package",
1500+
info,
1501+
Some("2024-02-15T00:00:00.000Z"),
1502+
Some(&version_requirement),
1503+
)?,
1504+
Version::parse("6.0.2")?
1505+
);
1506+
Ok(())
1507+
}
1508+
1509+
#[test]
1510+
fn test_select_npm_package_version_allows_requested_prerelease_before_cutoff() -> Result<()> {
1511+
let info: NpmInfo = serde_json::from_str(
1512+
r#"{
1513+
"dist-tags": { "latest": "7.0.0" },
1514+
"versions": ["7.1.0-beta.1", "7.1.0-beta.2", "7.0.0"],
1515+
"time": {
1516+
"7.0.0": "2024-01-01T00:00:00.000Z",
1517+
"7.1.0-beta.1": "2024-02-01T00:00:00.000Z",
1518+
"7.1.0-beta.2": "2024-03-01T00:00:00.000Z"
1519+
}
1520+
}"#,
1521+
)?;
1522+
let version_requirement = VersionReq::parse(">=7.1.0-beta.1, <7.1.0")?;
1523+
1524+
assert_eq!(
1525+
select_npm_package_version(
1526+
"test-package",
1527+
info,
1528+
Some("2024-02-15T00:00:00.000Z"),
1529+
Some(&version_requirement),
1530+
)?,
1531+
Version::parse("7.1.0-beta.1")?
1532+
);
1533+
Ok(())
1534+
}
1535+
1536+
#[test]
1537+
fn test_select_npm_package_version_errors_without_matching_version() -> Result<()> {
1538+
let info: NpmInfo = serde_json::from_str(
1539+
r#"{
1540+
"dist-tags": { "latest": "7.0.0" },
1541+
"versions": ["5.9.3", "7.0.0"]
1542+
}"#,
1543+
)?;
1544+
let version_requirement = VersionReq::parse("^6")?;
1545+
1546+
let error =
1547+
select_npm_package_version("test-package", info, None, Some(&version_requirement))
1548+
.expect_err("expected version requirement to reject all package versions");
1549+
assert_eq!(
1550+
error.to_string(),
1551+
"no version found for npm package test-package"
1552+
);
1553+
Ok(())
1554+
}
1555+
1556+
#[test]
1557+
fn test_pinned_version_strategy_replaces_different_installed_version() -> Result<()> {
1558+
let pinned_version = Version::parse("6.0.3")?;
1559+
1560+
assert!(!should_install_npm_package_version(
1561+
&pinned_version,
1562+
VersionStrategy::Pin(&pinned_version)
1563+
));
1564+
assert!(should_install_npm_package_version(
1565+
&Version::parse("7.0.0")?,
1566+
VersionStrategy::Pin(&pinned_version)
1567+
));
1568+
Ok(())
1569+
}
1570+
14081571
#[test]
14091572
fn test_deserialize_npm_info_npm11_format() -> Result<()> {
14101573
let json = r#"{

0 commit comments

Comments
 (0)