Port changes to zed2
This commit is contained in:
parent
24dd1c5812
commit
09346fb9f1
8 changed files with 1059 additions and 501 deletions
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Context;
|
||||
use collections::HashMap;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use gpui::{AsyncAppContext, Model};
|
||||
use language::{language_settings::language_settings, Buffer, Diff};
|
||||
|
@ -7,11 +7,10 @@ use lsp::{LanguageServer, LanguageServerId};
|
|||
use node_runtime::NodeRuntime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::paths::DEFAULT_PRETTIER_DIR;
|
||||
use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR};
|
||||
|
||||
pub enum Prettier {
|
||||
Real(RealPrettier),
|
||||
|
@ -20,7 +19,6 @@ pub enum Prettier {
|
|||
}
|
||||
|
||||
pub struct RealPrettier {
|
||||
worktree_id: Option<usize>,
|
||||
default: bool,
|
||||
prettier_dir: PathBuf,
|
||||
server: Arc<LanguageServer>,
|
||||
|
@ -28,17 +26,10 @@ pub struct RealPrettier {
|
|||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct TestPrettier {
|
||||
worktree_id: Option<usize>,
|
||||
prettier_dir: PathBuf,
|
||||
default: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocateStart {
|
||||
pub worktree_root_path: Arc<Path>,
|
||||
pub starting_path: Arc<Path>,
|
||||
}
|
||||
|
||||
pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js";
|
||||
pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js");
|
||||
const PRETTIER_PACKAGE_NAME: &str = "prettier";
|
||||
|
@ -63,79 +54,106 @@ impl Prettier {
|
|||
".editorconfig",
|
||||
];
|
||||
|
||||
pub async fn locate(
|
||||
starting_path: Option<LocateStart>,
|
||||
fs: Arc<dyn Fs>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
fn is_node_modules(path_component: &std::path::Component<'_>) -> bool {
|
||||
path_component.as_os_str().to_string_lossy() == "node_modules"
|
||||
pub async fn locate_prettier_installation(
|
||||
fs: &dyn Fs,
|
||||
installed_prettiers: &HashSet<PathBuf>,
|
||||
locate_from: &Path,
|
||||
) -> anyhow::Result<Option<PathBuf>> {
|
||||
let mut path_to_check = locate_from
|
||||
.components()
|
||||
.take_while(|component| component.as_os_str().to_string_lossy() != "node_modules")
|
||||
.collect::<PathBuf>();
|
||||
let path_to_check_metadata = fs
|
||||
.metadata(&path_to_check)
|
||||
.await
|
||||
.with_context(|| format!("failed to get metadata for initial path {path_to_check:?}"))?
|
||||
.with_context(|| format!("empty metadata for initial path {path_to_check:?}"))?;
|
||||
if !path_to_check_metadata.is_dir {
|
||||
path_to_check.pop();
|
||||
}
|
||||
|
||||
let paths_to_check = match starting_path.as_ref() {
|
||||
Some(starting_path) => {
|
||||
let worktree_root = starting_path
|
||||
.worktree_root_path
|
||||
.components()
|
||||
.into_iter()
|
||||
.take_while(|path_component| !is_node_modules(path_component))
|
||||
.collect::<PathBuf>();
|
||||
if worktree_root != starting_path.worktree_root_path.as_ref() {
|
||||
vec![worktree_root]
|
||||
let mut project_path_with_prettier_dependency = None;
|
||||
loop {
|
||||
if installed_prettiers.contains(&path_to_check) {
|
||||
log::debug!("Found prettier path {path_to_check:?} in installed prettiers");
|
||||
return Ok(Some(path_to_check));
|
||||
} else if let Some(package_json_contents) =
|
||||
read_package_json(fs, &path_to_check).await?
|
||||
{
|
||||
if has_prettier_in_package_json(&package_json_contents) {
|
||||
if has_prettier_in_node_modules(fs, &path_to_check).await? {
|
||||
log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules");
|
||||
return Ok(Some(path_to_check));
|
||||
} else if project_path_with_prettier_dependency.is_none() {
|
||||
project_path_with_prettier_dependency = Some(path_to_check.clone());
|
||||
}
|
||||
} else {
|
||||
if starting_path.starting_path.as_ref() == Path::new("") {
|
||||
worktree_root
|
||||
.parent()
|
||||
.map(|path| vec![path.to_path_buf()])
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
let file_to_format = starting_path.starting_path.as_ref();
|
||||
let mut paths_to_check = VecDeque::new();
|
||||
let mut current_path = worktree_root;
|
||||
for path_component in file_to_format.components().into_iter() {
|
||||
let new_path = current_path.join(path_component);
|
||||
let old_path = std::mem::replace(&mut current_path, new_path);
|
||||
paths_to_check.push_front(old_path);
|
||||
if is_node_modules(&path_component) {
|
||||
break;
|
||||
}
|
||||
match package_json_contents.get("workspaces") {
|
||||
Some(serde_json::Value::Array(workspaces)) => {
|
||||
match &project_path_with_prettier_dependency {
|
||||
Some(project_path_with_prettier_dependency) => {
|
||||
let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
|
||||
if workspaces.iter().filter_map(|value| {
|
||||
if let serde_json::Value::String(s) = value {
|
||||
Some(s.clone())
|
||||
} else {
|
||||
log::warn!("Skipping non-string 'workspaces' value: {value:?}");
|
||||
None
|
||||
}
|
||||
}).any(|workspace_definition| {
|
||||
if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() {
|
||||
path_matcher.is_match(subproject_path)
|
||||
} else {
|
||||
workspace_definition == subproject_path.to_string_lossy()
|
||||
}
|
||||
}) {
|
||||
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
|
||||
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
|
||||
return Ok(Some(path_to_check));
|
||||
} else {
|
||||
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json");
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
|
||||
None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
|
||||
}
|
||||
Vec::from(paths_to_check)
|
||||
}
|
||||
}
|
||||
|
||||
if !path_to_check.pop() {
|
||||
match project_path_with_prettier_dependency {
|
||||
Some(closest_prettier_discovered) => {
|
||||
anyhow::bail!("No prettier found in node_modules for ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}")
|
||||
}
|
||||
None => {
|
||||
log::debug!("Found no prettier in ancestors of {locate_from:?}");
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
match find_closest_prettier_dir(paths_to_check, fs.as_ref())
|
||||
.await
|
||||
.with_context(|| format!("finding prettier starting with {starting_path:?}"))?
|
||||
{
|
||||
Some(prettier_dir) => Ok(prettier_dir),
|
||||
None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn start(
|
||||
worktree_id: Option<usize>,
|
||||
_: LanguageServerId,
|
||||
prettier_dir: PathBuf,
|
||||
_: Arc<dyn NodeRuntime>,
|
||||
_: AsyncAppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
Self::Test(TestPrettier {
|
||||
worktree_id,
|
||||
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
|
||||
prettier_dir,
|
||||
}),
|
||||
)
|
||||
Ok(Self::Test(TestPrettier {
|
||||
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
|
||||
prettier_dir,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
pub async fn start(
|
||||
worktree_id: Option<usize>,
|
||||
server_id: LanguageServerId,
|
||||
prettier_dir: PathBuf,
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
|
@ -174,7 +192,6 @@ impl Prettier {
|
|||
.await
|
||||
.context("prettier server initialization")?;
|
||||
Ok(Self::Real(RealPrettier {
|
||||
worktree_id,
|
||||
server,
|
||||
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
|
||||
prettier_dir,
|
||||
|
@ -370,64 +387,61 @@ impl Prettier {
|
|||
Self::Test(test_prettier) => &test_prettier.prettier_dir,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn worktree_id(&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Real(local) => local.worktree_id,
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
Self::Test(test_prettier) => test_prettier.worktree_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_closest_prettier_dir(
|
||||
paths_to_check: Vec<PathBuf>,
|
||||
fs: &dyn Fs,
|
||||
) -> anyhow::Result<Option<PathBuf>> {
|
||||
for path in paths_to_check {
|
||||
let possible_package_json = path.join("package.json");
|
||||
if let Some(package_json_metadata) = fs
|
||||
.metadata(&possible_package_json)
|
||||
.await
|
||||
.with_context(|| format!("Fetching metadata for {possible_package_json:?}"))?
|
||||
{
|
||||
if !package_json_metadata.is_dir && !package_json_metadata.is_symlink {
|
||||
let package_json_contents = fs
|
||||
.load(&possible_package_json)
|
||||
.await
|
||||
.with_context(|| format!("reading {possible_package_json:?} file contents"))?;
|
||||
if let Ok(json_contents) = serde_json::from_str::<HashMap<String, serde_json::Value>>(
|
||||
&package_json_contents,
|
||||
) {
|
||||
if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") {
|
||||
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
}
|
||||
if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies")
|
||||
{
|
||||
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result<bool> {
|
||||
let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
|
||||
if let Some(node_modules_location_metadata) = fs
|
||||
.metadata(&possible_node_modules_location)
|
||||
.await
|
||||
.with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
|
||||
{
|
||||
return Ok(node_modules_location_metadata.is_dir);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
|
||||
if let Some(node_modules_location_metadata) = fs
|
||||
.metadata(&possible_node_modules_location)
|
||||
.await
|
||||
.with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
|
||||
{
|
||||
if node_modules_location_metadata.is_dir {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
async fn read_package_json(
|
||||
fs: &dyn Fs,
|
||||
path: &Path,
|
||||
) -> anyhow::Result<Option<HashMap<String, serde_json::Value>>> {
|
||||
let possible_package_json = path.join("package.json");
|
||||
if let Some(package_json_metadata) = fs
|
||||
.metadata(&possible_package_json)
|
||||
.await
|
||||
.with_context(|| format!("fetching metadata for package json {possible_package_json:?}"))?
|
||||
{
|
||||
if !package_json_metadata.is_dir && !package_json_metadata.is_symlink {
|
||||
let package_json_contents = fs
|
||||
.load(&possible_package_json)
|
||||
.await
|
||||
.with_context(|| format!("reading {possible_package_json:?} file contents"))?;
|
||||
return serde_json::from_str::<HashMap<String, serde_json::Value>>(
|
||||
&package_json_contents,
|
||||
)
|
||||
.map(Some)
|
||||
.with_context(|| format!("parsing {possible_package_json:?} file contents"));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn has_prettier_in_package_json(
|
||||
package_json_contents: &HashMap<String, serde_json::Value>,
|
||||
) -> bool {
|
||||
if let Some(serde_json::Value::Object(o)) = package_json_contents.get("dependencies") {
|
||||
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(serde_json::Value::Object(o)) = package_json_contents.get("devDependencies") {
|
||||
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
enum Format {}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -466,3 +480,316 @@ impl lsp::request::Request for ClearCache {
|
|||
type Result = ();
|
||||
const METHOD: &'static str = "prettier/clear_cache";
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fs::FakeFs;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
".config": {
|
||||
"zed": {
|
||||
"settings.json": r#"{ "formatter": "auto" }"#,
|
||||
},
|
||||
},
|
||||
"work": {
|
||||
"project": {
|
||||
"src": {
|
||||
"index.js": "// index.js file contents",
|
||||
},
|
||||
"node_modules": {
|
||||
"expect": {
|
||||
"build": {
|
||||
"print.js": "// print.js file contents",
|
||||
},
|
||||
"package.json": r#"{
|
||||
"devDependencies": {
|
||||
"prettier": "2.5.1"
|
||||
}
|
||||
}"#,
|
||||
},
|
||||
"prettier": {
|
||||
"index.js": "// Dummy prettier package file",
|
||||
},
|
||||
},
|
||||
"package.json": r#"{}"#
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/.config/zed/settings.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"Should successfully find no prettier for path hierarchy without it"
|
||||
);
|
||||
assert!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/project/src/index.js")
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
|
||||
);
|
||||
assert!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/project/node_modules/expect/build/print.js")
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"Even though it has package.json with prettier in it and no prettier on node_modules along the path, nothing should fail since declared inside node_modules"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"web_blog": {
|
||||
"node_modules": {
|
||||
"prettier": {
|
||||
"index.js": "// Dummy prettier package file",
|
||||
},
|
||||
"expect": {
|
||||
"build": {
|
||||
"print.js": "// print.js file contents",
|
||||
},
|
||||
"package.json": r#"{
|
||||
"devDependencies": {
|
||||
"prettier": "2.5.1"
|
||||
}
|
||||
}"#,
|
||||
},
|
||||
},
|
||||
"pages": {
|
||||
"[slug].tsx": "// [slug].tsx file contents",
|
||||
},
|
||||
"package.json": r#"{
|
||||
"devDependencies": {
|
||||
"prettier": "2.3.0"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 80,
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"tabWidth": 4
|
||||
}
|
||||
}"#
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/web_blog/pages/[slug].tsx")
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(PathBuf::from("/root/web_blog")),
|
||||
"Should find a preinstalled prettier in the project root"
|
||||
);
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/web_blog/node_modules/expect/build/print.js")
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(PathBuf::from("/root/web_blog")),
|
||||
"Should find a preinstalled prettier in the project root even for node_modules files"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"work": {
|
||||
"web_blog": {
|
||||
"pages": {
|
||||
"[slug].tsx": "// [slug].tsx file contents",
|
||||
},
|
||||
"package.json": r#"{
|
||||
"devDependencies": {
|
||||
"prettier": "2.3.0"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 80,
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"tabWidth": 4
|
||||
}
|
||||
}"#
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let path = "/root/work/web_blog/node_modules/pages/[slug].tsx";
|
||||
match Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new(path)
|
||||
)
|
||||
.await {
|
||||
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
|
||||
Err(e) => {
|
||||
let message = e.to_string();
|
||||
assert!(message.contains(path), "Error message should mention which start file was used for location");
|
||||
assert!(message.contains("/root/work/web_blog"), "Error message should mention potential candidates without prettier node_modules contents");
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::from_iter(
|
||||
[PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
|
||||
),
|
||||
Path::new("/root/work/web_blog/node_modules/pages/[slug].tsx")
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(PathBuf::from("/root/work")),
|
||||
"Should return first cached value found without path checks"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"work": {
|
||||
"full-stack-foundations": {
|
||||
"exercises": {
|
||||
"03.loading": {
|
||||
"01.problem.loader": {
|
||||
"app": {
|
||||
"routes": {
|
||||
"users+": {
|
||||
"$username_+": {
|
||||
"notes.tsx": "// notes.tsx file contents",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"node_modules": {},
|
||||
"package.json": r#"{
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.3"
|
||||
}
|
||||
}"#
|
||||
},
|
||||
},
|
||||
},
|
||||
"package.json": r#"{
|
||||
"workspaces": ["exercises/*/*", "examples/*"]
|
||||
}"#,
|
||||
"node_modules": {
|
||||
"prettier": {
|
||||
"index.js": "// Dummy prettier package file",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"),
|
||||
).await.unwrap(),
|
||||
Some(PathBuf::from("/root/work/full-stack-foundations")),
|
||||
"Should ascend to the multi-workspace root and find the prettier there",
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_in_npm_workspaces_for_not_installed(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"work": {
|
||||
"full-stack-foundations": {
|
||||
"exercises": {
|
||||
"03.loading": {
|
||||
"01.problem.loader": {
|
||||
"app": {
|
||||
"routes": {
|
||||
"users+": {
|
||||
"$username_+": {
|
||||
"notes.tsx": "// notes.tsx file contents",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"node_modules": {},
|
||||
"package.json": r#"{
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.3"
|
||||
}
|
||||
}"#
|
||||
},
|
||||
},
|
||||
},
|
||||
"package.json": r#"{
|
||||
"workspaces": ["exercises/*/*", "examples/*"]
|
||||
}"#,
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
match Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx")
|
||||
)
|
||||
.await {
|
||||
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
|
||||
Err(e) => {
|
||||
let message = e.to_string();
|
||||
assert!(message.contains("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader"), "Error message should mention which project had prettier defined");
|
||||
assert!(message.contains("/root/work/full-stack-foundations"), "Error message should mention potential candidates without prettier node_modules contents");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
const { Buffer } = require('buffer');
|
||||
const { Buffer } = require("buffer");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { once } = require('events');
|
||||
const { once } = require("events");
|
||||
|
||||
const prettierContainerPath = process.argv[2];
|
||||
if (prettierContainerPath == null || prettierContainerPath.length == 0) {
|
||||
process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`);
|
||||
process.stderr.write(
|
||||
`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.stat(prettierContainerPath, (err, stats) => {
|
||||
|
@ -19,7 +21,7 @@ fs.stat(prettierContainerPath, (err, stats) => {
|
|||
process.exit(1);
|
||||
}
|
||||
});
|
||||
const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier');
|
||||
const prettierPath = path.join(prettierContainerPath, "node_modules/prettier");
|
||||
|
||||
class Prettier {
|
||||
constructor(path, prettier, config) {
|
||||
|
@ -34,7 +36,7 @@ class Prettier {
|
|||
let config;
|
||||
try {
|
||||
prettier = await loadPrettier(prettierPath);
|
||||
config = await prettier.resolveConfig(prettierPath) || {};
|
||||
config = (await prettier.resolveConfig(prettierPath)) || {};
|
||||
} catch (e) {
|
||||
process.stderr.write(`Failed to load prettier: ${e}\n`);
|
||||
process.exit(1);
|
||||
|
@ -42,7 +44,7 @@ class Prettier {
|
|||
process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`);
|
||||
process.stdin.resume();
|
||||
handleBuffer(new Prettier(prettierPath, prettier, config));
|
||||
})()
|
||||
})();
|
||||
|
||||
async function handleBuffer(prettier) {
|
||||
for await (const messageText of readStdin()) {
|
||||
|
@ -54,22 +56,29 @@ async function handleBuffer(prettier) {
|
|||
continue;
|
||||
}
|
||||
// allow concurrent request handling by not `await`ing the message handling promise (async function)
|
||||
handleMessage(message, prettier).catch(e => {
|
||||
sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) });
|
||||
handleMessage(message, prettier).catch((e) => {
|
||||
const errorMessage = message;
|
||||
if ((errorMessage.params || {}).text !== undefined) {
|
||||
errorMessage.params.text = "..snip..";
|
||||
}
|
||||
sendResponse({
|
||||
id: message.id,
|
||||
...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const headerSeparator = "\r\n";
|
||||
const contentLengthHeaderName = 'Content-Length';
|
||||
const contentLengthHeaderName = "Content-Length";
|
||||
|
||||
async function* readStdin() {
|
||||
let buffer = Buffer.alloc(0);
|
||||
let streamEnded = false;
|
||||
process.stdin.on('end', () => {
|
||||
process.stdin.on("end", () => {
|
||||
streamEnded = true;
|
||||
});
|
||||
process.stdin.on('data', (data) => {
|
||||
process.stdin.on("data", (data) => {
|
||||
buffer = Buffer.concat([buffer, data]);
|
||||
});
|
||||
|
||||
|
@ -77,7 +86,7 @@ async function* readStdin() {
|
|||
sendResponse(makeError(errorMessage));
|
||||
buffer = Buffer.alloc(0);
|
||||
messageLength = null;
|
||||
await once(process.stdin, 'readable');
|
||||
await once(process.stdin, "readable");
|
||||
streamEnded = false;
|
||||
}
|
||||
|
||||
|
@ -88,20 +97,25 @@ async function* readStdin() {
|
|||
if (messageLength === null) {
|
||||
while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) {
|
||||
if (streamEnded) {
|
||||
await handleStreamEnded('Unexpected end of stream: headers not found');
|
||||
await handleStreamEnded("Unexpected end of stream: headers not found");
|
||||
continue main_loop;
|
||||
} else if (buffer.length > contentLengthHeaderName.length * 10) {
|
||||
await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`);
|
||||
await handleStreamEnded(
|
||||
`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`,
|
||||
);
|
||||
continue main_loop;
|
||||
}
|
||||
await once(process.stdin, 'readable');
|
||||
await once(process.stdin, "readable");
|
||||
}
|
||||
const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii');
|
||||
const contentLengthHeader = headers.split(headerSeparator)
|
||||
.map(header => header.split(':'))
|
||||
.filter(header => header[2] === undefined)
|
||||
.filter(header => (header[1] || '').length > 0)
|
||||
.find(header => (header[0] || '').trim() === contentLengthHeaderName);
|
||||
const headers = buffer
|
||||
.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`))
|
||||
.toString("ascii");
|
||||
const contentLengthHeader = headers
|
||||
.split(headerSeparator)
|
||||
.map((header) => header.split(":"))
|
||||
.filter((header) => header[2] === undefined)
|
||||
.filter((header) => (header[1] || "").length > 0)
|
||||
.find((header) => (header[0] || "").trim() === contentLengthHeaderName);
|
||||
const contentLength = (contentLengthHeader || [])[1];
|
||||
if (contentLength === undefined) {
|
||||
await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`);
|
||||
|
@ -111,13 +125,14 @@ async function* readStdin() {
|
|||
messageLength = parseInt(contentLength, 10);
|
||||
}
|
||||
|
||||
while (buffer.length < (headersLength + messageLength)) {
|
||||
while (buffer.length < headersLength + messageLength) {
|
||||
if (streamEnded) {
|
||||
await handleStreamEnded(
|
||||
`Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`);
|
||||
`Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`,
|
||||
);
|
||||
continue main_loop;
|
||||
}
|
||||
await once(process.stdin, 'readable');
|
||||
await once(process.stdin, "readable");
|
||||
}
|
||||
|
||||
const messageEnd = headersLength + messageLength;
|
||||
|
@ -125,12 +140,12 @@ async function* readStdin() {
|
|||
buffer = buffer.subarray(messageEnd);
|
||||
headersLength = null;
|
||||
messageLength = null;
|
||||
yield message.toString('utf8');
|
||||
yield message.toString("utf8");
|
||||
}
|
||||
} catch (e) {
|
||||
sendResponse(makeError(`Error reading stdin: ${e}`));
|
||||
} finally {
|
||||
process.stdin.off('data', () => { });
|
||||
process.stdin.off("data", () => {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +158,7 @@ async function handleMessage(message, prettier) {
|
|||
throw new Error(`Message id is undefined: ${JSON.stringify(message)}`);
|
||||
}
|
||||
|
||||
if (method === 'prettier/format') {
|
||||
if (method === "prettier/format") {
|
||||
if (params === undefined || params.text === undefined) {
|
||||
throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`);
|
||||
}
|
||||
|
@ -153,7 +168,7 @@ async function handleMessage(message, prettier) {
|
|||
|
||||
let resolvedConfig = {};
|
||||
if (params.options.filepath !== undefined) {
|
||||
resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {};
|
||||
resolvedConfig = (await prettier.prettier.resolveConfig(params.options.filepath)) || {};
|
||||
}
|
||||
|
||||
const options = {
|
||||
|
@ -161,21 +176,25 @@ async function handleMessage(message, prettier) {
|
|||
...resolvedConfig,
|
||||
parser: params.options.parser,
|
||||
plugins: params.options.plugins,
|
||||
path: params.options.filepath
|
||||
path: params.options.filepath,
|
||||
};
|
||||
process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`);
|
||||
process.stderr.write(
|
||||
`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${
|
||||
params.options.filepath || ""
|
||||
}' with options: ${JSON.stringify(options)}\n`,
|
||||
);
|
||||
const formattedText = await prettier.prettier.format(params.text, options);
|
||||
sendResponse({ id, result: { text: formattedText } });
|
||||
} else if (method === 'prettier/clear_cache') {
|
||||
} else if (method === "prettier/clear_cache") {
|
||||
prettier.prettier.clearConfigCache();
|
||||
prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {};
|
||||
prettier.config = (await prettier.prettier.resolveConfig(prettier.path)) || {};
|
||||
sendResponse({ id, result: null });
|
||||
} else if (method === 'initialize') {
|
||||
} else if (method === "initialize") {
|
||||
sendResponse({
|
||||
id,
|
||||
result: {
|
||||
"capabilities": {}
|
||||
}
|
||||
capabilities: {},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
|
@ -185,18 +204,20 @@ async function handleMessage(message, prettier) {
|
|||
function makeError(message) {
|
||||
return {
|
||||
error: {
|
||||
"code": -32600, // invalid request code
|
||||
code: -32600, // invalid request code
|
||||
message,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sendResponse(response) {
|
||||
const responsePayloadString = JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
...response
|
||||
...response,
|
||||
});
|
||||
const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`;
|
||||
const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(
|
||||
responsePayloadString,
|
||||
)}${headerSeparator}${headerSeparator}`;
|
||||
process.stdout.write(headers + responsePayloadString);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue