|
| 1 | +use std::ffi::OsStr; |
| 2 | + |
| 3 | +use oxc_allocator::Allocator; |
| 4 | +use oxc_formatter::{FormatOptions, Formatter}; |
| 5 | +use oxc_parser::{ParseOptions, Parser}; |
| 6 | +use oxc_span::SourceType; |
| 7 | +use tower_lsp_server::{ |
| 8 | + UriExt, |
| 9 | + lsp_types::{Position, Range, TextEdit, Uri}, |
| 10 | +}; |
| 11 | + |
| 12 | +use crate::LSP_MAX_INT; |
| 13 | + |
| 14 | +pub struct ServerFormatter; |
| 15 | + |
| 16 | +impl ServerFormatter { |
| 17 | + pub fn new() -> Self { |
| 18 | + Self {} |
| 19 | + } |
| 20 | + |
| 21 | + #[expect(clippy::unused_self)] |
| 22 | + pub fn run_single(&self, uri: &Uri) -> Option<Vec<TextEdit>> { |
| 23 | + let path = uri.to_file_path()?; |
| 24 | + let source_type = get_supported_source_type(&path)?; |
| 25 | + let source_text = std::fs::read_to_string(path).expect("Failed to read file"); |
| 26 | + |
| 27 | + let allocator = Allocator::new(); |
| 28 | + let ret = Parser::new(&allocator, &source_text, source_type) |
| 29 | + .with_options(ParseOptions { |
| 30 | + parse_regular_expression: false, |
| 31 | + // Enable all syntax features |
| 32 | + allow_v8_intrinsics: true, |
| 33 | + allow_return_outside_function: true, |
| 34 | + // `oxc_formatter` expects this to be false |
| 35 | + preserve_parens: false, |
| 36 | + }) |
| 37 | + .parse(); |
| 38 | + |
| 39 | + if !ret.errors.is_empty() { |
| 40 | + return None; |
| 41 | + } |
| 42 | + |
| 43 | + let options = FormatOptions { |
| 44 | + // semicolons: "always".parse().unwrap(), |
| 45 | + semicolons: "as-needed".parse().unwrap(), |
| 46 | + ..FormatOptions::default() |
| 47 | + }; |
| 48 | + let code = Formatter::new(&allocator, options).build(&ret.program); |
| 49 | + |
| 50 | + Some(vec![TextEdit::new( |
| 51 | + Range::new(Position::new(0, 0), Position::new(LSP_MAX_INT, 0)), |
| 52 | + code, |
| 53 | + )]) |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +// Additional extensions from linguist-languages, which Prettier also supports |
| 58 | +// - https://github.com/ikatyang-collab/linguist-languages/blob/d1dc347c7ced0f5b42dd66c7d1c4274f64a3eb6b/data/JavaScript.js |
| 59 | +// No special extensions for TypeScript |
| 60 | +// - https://github.com/ikatyang-collab/linguist-languages/blob/d1dc347c7ced0f5b42dd66c7d1c4274f64a3eb6b/data/TypeScript.js |
| 61 | +const ADDITIONAL_JS_EXTENSIONS: &[&str] = &[ |
| 62 | + "_js", |
| 63 | + "bones", |
| 64 | + "es", |
| 65 | + "es6", |
| 66 | + "frag", |
| 67 | + "gs", |
| 68 | + "jake", |
| 69 | + "javascript", |
| 70 | + "jsb", |
| 71 | + "jscad", |
| 72 | + "jsfl", |
| 73 | + "jslib", |
| 74 | + "jsm", |
| 75 | + "jspre", |
| 76 | + "jss", |
| 77 | + "njs", |
| 78 | + "pac", |
| 79 | + "sjs", |
| 80 | + "ssjs", |
| 81 | + "xsjs", |
| 82 | + "xsjslib", |
| 83 | +]; |
| 84 | + |
| 85 | +fn get_supported_source_type(path: &std::path::Path) -> Option<SourceType> { |
| 86 | + let extension = path.extension()?.to_string_lossy(); |
| 87 | + |
| 88 | + // Standard extensions, also supported by `oxc_span::VALID_EXTENSIONS` |
| 89 | + if let Ok(source_type) = SourceType::from_extension(&extension) { |
| 90 | + return Some(source_type); |
| 91 | + } |
| 92 | + // Additional extensions from linguist-languages, which Prettier also supports |
| 93 | + if ADDITIONAL_JS_EXTENSIONS.contains(&extension.as_ref()) { |
| 94 | + return Some(SourceType::default()); |
| 95 | + } |
| 96 | + // `Jakefile` has no extension but is a valid JS file defined by linguist-languages |
| 97 | + if path.file_name() == Some(OsStr::new("Jakefile")) { |
| 98 | + return Some(SourceType::default()); |
| 99 | + } |
| 100 | + |
| 101 | + None |
| 102 | +} |
0 commit comments