diff --git a/crates/oxc_language_server/src/linter.rs b/crates/oxc_language_server/src/linter.rs index 95b1156c8..f86ac9ffa 100644 --- a/crates/oxc_language_server/src/linter.rs +++ b/crates/oxc_language_server/src/linter.rs @@ -8,12 +8,17 @@ use std::{ }, }; -use crate::options::LintOptions; use crate::walk::Walk; +use crate::{options::LintOptions, walk::Extensions}; use miette::NamedSource; use oxc_allocator::Allocator; use oxc_diagnostics::{miette, Error, Severity}; -use oxc_linter::{LintContext, LintSettings, Linter}; +use oxc_linter::{ + partial_loader::{ + vue_partial_loader::VuePartialLoader, PartialLoader, LINT_PARTIAL_LOADER_EXT, + }, + LintContext, LintSettings, Linter, +}; use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; @@ -223,8 +228,8 @@ impl IsolatedLintHandler { } fn is_wanted_ext(path: &Path) -> bool { - path.extension() - .map_or(false, |ext| VALID_EXTENSIONS.contains(&ext.to_string_lossy().as_ref())) + let extensions = get_extensions(); + path.extension().map_or(false, |ext| extensions.contains(&ext.to_string_lossy().as_ref())) } fn process_paths( @@ -234,7 +239,7 @@ impl IsolatedLintHandler { ) { let (tx_path, rx_path) = mpsc::channel::>(); - let walk = Walk::new(&self.options); + let walk = Walk::new(&self.options).with_extensions(Extensions(get_extensions())); let number_of_files = Arc::clone(number_of_files); rayon::spawn(move || { let mut count = 0; @@ -276,19 +281,37 @@ impl IsolatedLintHandler { .collect() } + fn get_source_type_and_text( + path: &Path, + source_text: Option, + ) -> Option<(SourceType, String)> { + let read_file = |path: &Path| -> String { + if let Some(source_text) = source_text { + return source_text; + } + fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read {path:?}")) + }; + + if let Ok(source_type) = SourceType::from_path(path) { + return Some((source_type, read_file(path))); + } + let ext = path.extension().and_then(std::ffi::OsStr::to_str)?; + let partial_loader = if ext == "vue" { Some(PartialLoader::Vue) } else { None }; + let partial_loader = partial_loader?; + + let source_text = read_file(path); + let ret = partial_loader.parse(&source_text); + Some((ret.source_type, ret.source_text)) + } + fn lint_path( linter: &Linter, path: &Path, plugin: Plugin, source_text: Option, ) -> Option<(PathBuf, Vec)> { - let source_text = source_text.unwrap_or_else(|| { - fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read {path:?}")) - }); - + let (source_type, source_text) = Self::get_source_type_and_text(path, source_text)?; let allocator = Allocator::default(); - let source_type = - SourceType::from_path(path).unwrap_or_else(|_| panic!("Incorrect {path:?}")); let ret = Parser::new(&allocator, &source_text, source_type) .allow_return_outside_function(true) .parse(); @@ -389,6 +412,14 @@ impl IsolatedLintHandler { } } +fn get_extensions() -> Vec<&'static str> { + VALID_EXTENSIONS + .iter() + .chain(LINT_PARTIAL_LOADER_EXT.iter()) + .copied() + .collect::>() +} + #[allow(clippy::cast_possible_truncation)] fn offset_to_position(offset: usize, source_text: &str) -> Option { let rope = Rope::from_str(source_text); diff --git a/crates/oxc_language_server/src/walk.rs b/crates/oxc_language_server/src/walk.rs index 8420fd44c..c799f8b5f 100644 --- a/crates/oxc_language_server/src/walk.rs +++ b/crates/oxc_language_server/src/walk.rs @@ -4,9 +4,17 @@ use ignore::{overrides::OverrideBuilder, DirEntry, WalkBuilder}; use oxc_span::VALID_EXTENSIONS; use crate::options::LintOptions; +pub struct Extensions(pub Vec<&'static str>); + +impl Default for Extensions { + fn default() -> Self { + Self(VALID_EXTENSIONS.to_vec()) + } +} pub struct Walk { inner: ignore::Walk, + extensions: Extensions, } impl Walk { @@ -36,17 +44,26 @@ impl Walk { // * following symlinks is a really slow syscall // * it is super rare to have symlinked source code let inner = inner.ignore(false).git_global(false).follow_links(false).build(); - Self { inner } + Self { inner, extensions: Extensions::default() } + } + + pub fn with_extensions(mut self, extensions: Extensions) -> Self { + self.extensions = extensions; + self } pub fn iter(self) -> impl Iterator> { - self.inner - .filter_map(Result::ok) - .filter(Self::is_wanted_entry) - .map(|entry| entry.path().to_path_buf().into_boxed_path()) + let extensions = self.extensions; + self.inner.filter_map(Result::ok).filter_map(move |dir_entry| { + if Self::is_wanted_entry(&dir_entry, &extensions) { + Some(dir_entry.path().to_path_buf().into_boxed_path()) + } else { + None + } + }) } - pub fn is_wanted_entry(dir_entry: &DirEntry) -> bool { + pub fn is_wanted_entry(dir_entry: &DirEntry, extensions: &Extensions) -> bool { let Some(file_type) = dir_entry.file_type() else { return false }; if file_type.is_dir() { return false; @@ -56,6 +73,6 @@ impl Walk { return false; } let Some(extension) = dir_entry.path().extension() else { return false }; - VALID_EXTENSIONS.contains(&extension.to_string_lossy().as_ref()) + extensions.0.contains(&extension.to_string_lossy().as_ref()) } } diff --git a/editors/vscode/client/extension.ts b/editors/vscode/client/extension.ts index 1bd329a86..23fc69f85 100644 --- a/editors/vscode/client/extension.ts +++ b/editors/vscode/client/extension.ts @@ -125,6 +125,7 @@ export async function activate(context: ExtensionContext) { "javascript", "typescriptreact", "javascriptreact", + "vue" ].map((lang) => ({ language: lang, scheme: "file",