├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── extension.toml └── src └── oxc.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | extension.wasm 3 | node_modules 4 | package-lock.json 5 | pnpm-lock.yaml 6 | package.json 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.97" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "2.9.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 16 | 17 | [[package]] 18 | name = "equivalent" 19 | version = "1.0.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 22 | 23 | [[package]] 24 | name = "hashbrown" 25 | version = "0.15.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 28 | 29 | [[package]] 30 | name = "heck" 31 | version = "0.4.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 34 | dependencies = [ 35 | "unicode-segmentation", 36 | ] 37 | 38 | [[package]] 39 | name = "id-arena" 40 | version = "2.2.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 43 | 44 | [[package]] 45 | name = "indexmap" 46 | version = "2.8.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" 49 | dependencies = [ 50 | "equivalent", 51 | "hashbrown", 52 | "serde", 53 | ] 54 | 55 | [[package]] 56 | name = "itoa" 57 | version = "1.0.15" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 60 | 61 | [[package]] 62 | name = "leb128" 63 | version = "0.2.5" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 66 | 67 | [[package]] 68 | name = "log" 69 | version = "0.4.27" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 72 | 73 | [[package]] 74 | name = "memchr" 75 | version = "2.7.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 78 | 79 | [[package]] 80 | name = "proc-macro2" 81 | version = "1.0.94" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 84 | dependencies = [ 85 | "unicode-ident", 86 | ] 87 | 88 | [[package]] 89 | name = "quote" 90 | version = "1.0.40" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 93 | dependencies = [ 94 | "proc-macro2", 95 | ] 96 | 97 | [[package]] 98 | name = "ryu" 99 | version = "1.0.20" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 102 | 103 | [[package]] 104 | name = "semver" 105 | version = "1.0.26" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 108 | 109 | [[package]] 110 | name = "serde" 111 | version = "1.0.219" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 114 | dependencies = [ 115 | "serde_derive", 116 | ] 117 | 118 | [[package]] 119 | name = "serde_derive" 120 | version = "1.0.219" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | ] 128 | 129 | [[package]] 130 | name = "serde_json" 131 | version = "1.0.140" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 134 | dependencies = [ 135 | "itoa", 136 | "memchr", 137 | "ryu", 138 | "serde", 139 | ] 140 | 141 | [[package]] 142 | name = "smallvec" 143 | version = "1.14.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 146 | 147 | [[package]] 148 | name = "spdx" 149 | version = "0.10.8" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" 152 | dependencies = [ 153 | "smallvec", 154 | ] 155 | 156 | [[package]] 157 | name = "syn" 158 | version = "2.0.100" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 161 | dependencies = [ 162 | "proc-macro2", 163 | "quote", 164 | "unicode-ident", 165 | ] 166 | 167 | [[package]] 168 | name = "unicode-ident" 169 | version = "1.0.18" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 172 | 173 | [[package]] 174 | name = "unicode-segmentation" 175 | version = "1.12.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 178 | 179 | [[package]] 180 | name = "unicode-xid" 181 | version = "0.2.6" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 184 | 185 | [[package]] 186 | name = "wasm-encoder" 187 | version = "0.201.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" 190 | dependencies = [ 191 | "leb128", 192 | ] 193 | 194 | [[package]] 195 | name = "wasm-metadata" 196 | version = "0.201.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" 199 | dependencies = [ 200 | "anyhow", 201 | "indexmap", 202 | "serde", 203 | "serde_derive", 204 | "serde_json", 205 | "spdx", 206 | "wasm-encoder", 207 | "wasmparser", 208 | ] 209 | 210 | [[package]] 211 | name = "wasmparser" 212 | version = "0.201.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" 215 | dependencies = [ 216 | "bitflags", 217 | "indexmap", 218 | "semver", 219 | ] 220 | 221 | [[package]] 222 | name = "wit-bindgen" 223 | version = "0.22.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27" 226 | dependencies = [ 227 | "bitflags", 228 | "wit-bindgen-rt", 229 | "wit-bindgen-rust-macro", 230 | ] 231 | 232 | [[package]] 233 | name = "wit-bindgen-core" 234 | version = "0.22.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "e85e72719ffbccf279359ad071497e47eb0675fe22106dea4ed2d8a7fcb60ba4" 237 | dependencies = [ 238 | "anyhow", 239 | "wit-parser", 240 | ] 241 | 242 | [[package]] 243 | name = "wit-bindgen-rt" 244 | version = "0.22.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64" 247 | 248 | [[package]] 249 | name = "wit-bindgen-rust" 250 | version = "0.22.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" 253 | dependencies = [ 254 | "anyhow", 255 | "heck", 256 | "indexmap", 257 | "wasm-metadata", 258 | "wit-bindgen-core", 259 | "wit-component", 260 | ] 261 | 262 | [[package]] 263 | name = "wit-bindgen-rust-macro" 264 | version = "0.22.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "d376d3ae5850526dfd00d937faea0d81a06fa18f7ac1e26f386d760f241a8f4b" 267 | dependencies = [ 268 | "anyhow", 269 | "proc-macro2", 270 | "quote", 271 | "syn", 272 | "wit-bindgen-core", 273 | "wit-bindgen-rust", 274 | ] 275 | 276 | [[package]] 277 | name = "wit-component" 278 | version = "0.201.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" 281 | dependencies = [ 282 | "anyhow", 283 | "bitflags", 284 | "indexmap", 285 | "log", 286 | "serde", 287 | "serde_derive", 288 | "serde_json", 289 | "wasm-encoder", 290 | "wasm-metadata", 291 | "wasmparser", 292 | "wit-parser", 293 | ] 294 | 295 | [[package]] 296 | name = "wit-parser" 297 | version = "0.201.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" 300 | dependencies = [ 301 | "anyhow", 302 | "id-arena", 303 | "indexmap", 304 | "log", 305 | "semver", 306 | "serde", 307 | "serde_derive", 308 | "serde_json", 309 | "unicode-xid", 310 | "wasmparser", 311 | ] 312 | 313 | [[package]] 314 | name = "zed-oxc" 315 | version = "0.1.4" 316 | dependencies = [ 317 | "zed_extension_api", 318 | ] 319 | 320 | [[package]] 321 | name = "zed_extension_api" 322 | version = "0.3.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "5e580c3ea59a92fc876756f3b4612f7a47ec86a34a1b9be099a24bd55401ff31" 325 | dependencies = [ 326 | "serde", 327 | "serde_json", 328 | "wit-bindgen", 329 | ] 330 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zed-oxc" 3 | version = "0.1.4" 4 | edition = "2024" 5 | license = "MIT" 6 | publish = false 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | path = "src/oxc.rs" 11 | 12 | [dependencies] 13 | zed_extension_api = "0.3.0" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Biome Developers and Contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | OXC Logo 3 |

4 | 5 | # Oxc extension for Zed 6 | 7 | This extension adds support for [Oxc](https://github.com/oxc-project/oxc) in [Zed](https://zed.dev/). 8 | 9 | Languages currently supported: 10 | 11 | - **JavaScript** 12 | - **TypeScript** 13 | - **JSX** 14 | - **TSX** 15 | - **Vue.js** 16 | - **Astro** 17 | - **Svelte** 18 | 19 | ## Installation 20 | 21 | Requires Zed >= **v0.131.0**. 22 | 23 | This extension is available in the extensions view inside the Zed editor. Open `zed: extensions` and search for _Oxc_. 24 | -------------------------------------------------------------------------------- /extension.toml: -------------------------------------------------------------------------------- 1 | authors = ["suxiaoshao "] 2 | description = "Oxc support for Zed" 3 | id = "oxc" 4 | name = "Oxc" 5 | repository = "https://github.com/oxc-project/zed-oxc" 6 | schema_version = 1 7 | version = "0.1.4" 8 | 9 | [language_servers.oxc] 10 | code_actions_kind = ["", "quickfix"] 11 | language = "JavaScript" 12 | languages = [ 13 | "JavaScript", 14 | "JSX", 15 | "TypeScript", 16 | "TSX", 17 | "Vue.js", 18 | "Astro", 19 | "Svelte", 20 | ] 21 | name = "Oxc Language Server" 22 | 23 | [language_servers.oxc.language_ids] 24 | "Astro" = "astro" 25 | "JSX" = "javascriptreact" 26 | "JavaScript" = "javascript" 27 | "Svelte" = "svelte" 28 | "TSX" = "typescriptreact" 29 | "TypeScript" = "typescript" 30 | "Vue.js" = "vuejs" 31 | -------------------------------------------------------------------------------- /src/oxc.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, path::Path}; 2 | use zed_extension_api::{ 3 | self as zed, 4 | serde_json::{self, Value}, 5 | settings::LspSettings, 6 | LanguageServerId, Result, 7 | }; 8 | 9 | const SERVER_PATH: &str = "node_modules/oxlint/bin/oxc_language_server"; 10 | const PACKAGE_NAME: &str = "oxlint"; 11 | const FALLBACK_SERVER_PATH: &str = "./node_modules/.bin/oxc_language_server"; 12 | 13 | const OXC_CONFIG_PATHS: &[&str] = &[".oxlintrc.json"]; 14 | 15 | struct OxcExtension; 16 | 17 | impl OxcExtension { 18 | fn server_exists(&self, path: &Path) -> bool { 19 | fs::metadata(path).is_ok_and(|stat| stat.is_file()) 20 | } 21 | 22 | fn server_script_path( 23 | &mut self, 24 | language_server_id: &LanguageServerId, 25 | worktree: &zed::Worktree, 26 | ) -> Result { 27 | // This is a workaround, as reading the file from wasm doesn't work. 28 | // Instead we try to read the `package.json`, see if `@biomejs/biome` is installed 29 | let package_json = worktree 30 | .read_text_file("package.json") 31 | .unwrap_or(String::from(r#"{}"#)); 32 | let package_json: Option = 33 | serde_json::from_str(package_json.as_str()).ok(); 34 | 35 | let server_package_exists = package_json.is_some_and(|f| { 36 | !f["dependencies"][PACKAGE_NAME].is_null() 37 | || !f["devDependencies"][PACKAGE_NAME].is_null() 38 | }); 39 | 40 | if server_package_exists { 41 | let worktree_root_path = worktree.root_path(); 42 | let path = Path::new(worktree_root_path.as_str()) 43 | .join(SERVER_PATH) 44 | .to_string_lossy() 45 | .to_string(); 46 | return Ok(path); 47 | } 48 | 49 | // fallback to extension owned biome 50 | zed::set_language_server_installation_status( 51 | language_server_id, 52 | &zed::LanguageServerInstallationStatus::CheckingForUpdate, 53 | ); 54 | 55 | let fallback_server_path = Path::new(FALLBACK_SERVER_PATH); 56 | let version = zed::npm_package_latest_version(PACKAGE_NAME)?; 57 | 58 | if !self.server_exists(fallback_server_path) 59 | || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version) 60 | { 61 | zed::set_language_server_installation_status( 62 | language_server_id, 63 | &zed::LanguageServerInstallationStatus::Downloading, 64 | ); 65 | let result = zed::npm_install_package(PACKAGE_NAME, &version); 66 | match result { 67 | Ok(()) => { 68 | if !self.server_exists(fallback_server_path) { 69 | Err(format!( 70 | "installed package '{PACKAGE_NAME}' did not contain expected path '{fallback_server_path:?}'", 71 | ))?; 72 | } 73 | } 74 | Err(error) => { 75 | if !self.server_exists(fallback_server_path) { 76 | Err(format!( 77 | "failed to install package '{PACKAGE_NAME}': {error}" 78 | ))?; 79 | } 80 | } 81 | } 82 | } 83 | 84 | Ok(fallback_server_path.to_string_lossy().to_string()) 85 | } 86 | 87 | // Returns the path if a config file exists 88 | pub fn config_path(&self, worktree: &zed::Worktree, settings: &Value) -> Option { 89 | let config_path_setting = settings.get("config_path").and_then(|value| value.as_str()); 90 | 91 | if let Some(config_path) = config_path_setting { 92 | if worktree.read_text_file(config_path).is_ok() { 93 | return Some(config_path.to_string()); 94 | } else { 95 | return None; 96 | } 97 | } 98 | 99 | for config_path in OXC_CONFIG_PATHS { 100 | if worktree.read_text_file(config_path).is_ok() { 101 | return Some(config_path.to_string()); 102 | } 103 | } 104 | 105 | None 106 | } 107 | } 108 | 109 | impl zed_extension_api::Extension for OxcExtension { 110 | fn new() -> Self 111 | where 112 | Self: Sized, 113 | { 114 | Self 115 | } 116 | 117 | fn language_server_command( 118 | &mut self, 119 | language_server_id: &zed_extension_api::LanguageServerId, 120 | worktree: &zed_extension_api::Worktree, 121 | ) -> zed_extension_api::Result { 122 | let path = self.server_script_path(language_server_id, worktree)?; 123 | let settings = LspSettings::for_worktree(language_server_id.as_ref(), worktree)?; 124 | 125 | let mut args = vec![]; 126 | 127 | if let Some(settings) = settings.settings { 128 | let config_path = self.config_path(worktree, &settings); 129 | 130 | let require_config_file = settings 131 | .get("require_config_file") 132 | .and_then(|value| value.as_bool()) 133 | .unwrap_or(false); 134 | 135 | if let Some(config_path) = config_path { 136 | args.push("--config".to_string()); 137 | args.push(config_path.clone()); 138 | } else if require_config_file { 139 | return Err( 140 | ".oxlintrc.json is not found but require_config_file is true".to_string(), 141 | ); 142 | } 143 | } 144 | 145 | let bin = env::current_dir() 146 | .unwrap() 147 | .join(path) 148 | .to_string_lossy() 149 | .to_string(); 150 | 151 | if let Some(binary) = settings.binary { 152 | return Ok(zed::Command { 153 | command: binary.path.map_or(bin, |path| path), 154 | args: binary.arguments.map_or(args, |args| args), 155 | env: Default::default(), 156 | }); 157 | } 158 | 159 | Ok(zed::Command { 160 | command: bin, 161 | args, 162 | env: Default::default(), 163 | }) 164 | } 165 | } 166 | 167 | zed_extension_api::register_extension!(OxcExtension); 168 | --------------------------------------------------------------------------------