Файли
openidea/crates/ide-sdk/src/angular.rs
T
Mykhailo Smolinskyiтаnntndfrk 7a46774fda fix(angular): find ngserver installed through version managers (#260)
The Angular language server (ngserver) was not found when Node and global npm packages came from a version manager (nvm, fnm, volta, asdf, mise) whose bin directories are not on the GUI app's PATH. Tool lookup now searches those directories too through a shared ide-core tool_path module used by the LSP launcher, SDK queries and tool detection; explicit relative commands resolve against the project root, empty PATH entries are ignored, and a non-executable file earlier on PATH no longer shadows the real command.

The process query test that flaked under load now uses a generous deadline for the success case, with the timeout covered by a separate test of a child that never exits.

Co-authored-by: nntndfrk <[email protected]>
2026-10-30 12:23:25 +08:00

94 рядки
3.9 KiB
Rust
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
//! Angular Language Server (`ngserver`): its package, where TypeScript and
//! `@angular/language-service` come from, and the project's Angular version.
use crate::npm::{package_dir, shim_package};
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
pub(crate) const SERVER: &str = "angular-language-server";
const PACKAGE: &str = "@angular/language-server";
/// `@angular/language-server`'s directory for an `ngserver` executable found on
/// `path` and run in `cwd` with `env`.
pub(crate) fn server_package(bin: &Path, path: &OsStr, cwd: &Path, env: &BTreeMap<String, String>) -> Option<PathBuf> {
package_dir(bin, PACKAGE).or_else(|| shim_package(bin, PACKAGE, path, cwd, env))
}
/// Why an `ngserver` whose package isn't found can't start: it loads TypeScript
/// and `@angular/language-service` from there.
pub(crate) fn missing_package_message(bin: &Path) -> String {
format!(
"Can't find the @angular/language-server package of {}, which brings the TypeScript and Angular \
language service the server loads. Reinstall it in Settings › Languages & Frameworks › Language \
Servers or with `npm i -g @angular/language-server`.",
bin.display()
)
}
/// Why a server installed in a directory whose path has a comma can't start.
pub(crate) fn comma_message(package: &str) -> String {
format!(
"The Angular Language Server can't load from {package}: its path has a comma. Install it in a \
directory without one."
)
}
/// The version of the `@angular/core` that `dir` resolves (from its own or an
/// ancestor's `node_modules`, like Node).
pub(crate) fn core_version(dir: &Path) -> Option<String> {
dir.ancestors().find_map(|a| {
let text = std::fs::read_to_string(a.join("node_modules/@angular/core/package.json")).ok()?;
let json: serde_json::Value = serde_json::from_str(&text).ok()?;
json.get("version")?.as_str().map(str::to_owned)
})
}
/// Arguments telling the server the project's Angular `version`: templates of
/// projects before 17 have no blocks (`@if`), before 18.1 no `@let`.
pub(crate) fn version_args(version: &str) -> Vec<String> {
let mut parts = version.split(['.', '-', '+']).map(|p| p.parse::<u32>().ok());
let (Some(Some(major)), Some(Some(minor))) = (parts.next(), parts.next()) else { return Vec::new() };
// 0.0.0: a local build of Angular.
if major == 0 {
return Vec::new();
}
let mut args = vec!["--angularCoreVersion".to_owned(), version.to_owned()];
if major < 17 {
args.push("--disableBlockSyntax".into());
}
if (major, minor) < (18, 1) {
args.push("--disableLetSyntax".into());
}
args
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn template_syntax_follows_the_angular_version() {
let args = |v: &str| version_args(v).into_iter().skip(2).collect::<Vec<_>>();
assert_eq!(version_args("22.2.3")[..2], ["--angularCoreVersion", "22.2.3"]);
assert_eq!(args("22.0.0-next.1"), Vec::<String>::new());
assert_eq!(args("18.1.0"), Vec::<String>::new());
assert_eq!(args("18.0.5"), ["--disableLetSyntax"]);
assert_eq!(args("17.3.12"), ["--disableLetSyntax"]);
assert_eq!(args("16.2.0"), ["--disableBlockSyntax", "--disableLetSyntax"]);
assert!(version_args("0.0.0-PLACEHOLDER").is_empty());
assert!(version_args("next").is_empty());
}
#[test]
fn core_version_is_found_in_an_ancestor() {
let tmp = tempfile::tempdir().unwrap();
let core = tmp.path().join("node_modules/@angular/core");
std::fs::create_dir_all(&core).unwrap();
std::fs::write(core.join("package.json"), r#"{"name":"@angular/core","version":"19.2.1"}"#).unwrap();
let app = tmp.path().join("apps/web");
std::fs::create_dir_all(&app).unwrap();
assert_eq!(core_version(&app).as_deref(), Some("19.2.1"));
}
}