|
1 | 1 | use std::{ |
| 2 | + ffi::OsString, |
2 | 3 | path::{Path, PathBuf}, |
3 | 4 | sync::{Arc, OnceLock}, |
4 | 5 | }; |
5 | 6 |
|
6 | 7 | use log::debug; |
7 | | -use rustc_hash::FxHashSet; |
| 8 | +use rustc_hash::{FxHashMap, FxHashSet}; |
8 | 9 | use tower_lsp_server::{ |
9 | 10 | UriExt, |
10 | 11 | lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity, Uri}, |
11 | 12 | }; |
12 | 13 |
|
13 | 14 | use oxc_allocator::Allocator; |
14 | | -use oxc_linter::RuntimeFileSystem; |
| 15 | +use oxc_linter::{FileWalker, FileWalkerOptions, RuntimeFileSystem}; |
15 | 16 | use oxc_linter::{ |
16 | 17 | LINTABLE_EXTENSIONS, LintService, LintServiceOptions, Linter, MessageWithPosition, |
17 | 18 | loader::Loader, read_to_string, |
18 | 19 | }; |
19 | 20 |
|
| 21 | +use crate::uri_ext::path_to_uri; |
| 22 | + |
20 | 23 | use super::error_with_position::{ |
21 | 24 | DiagnosticReport, PossibleFixContent, message_with_position_to_lsp_diagnostic_report, |
22 | 25 | }; |
@@ -73,49 +76,71 @@ impl IsolatedLintHandler { |
73 | 76 | let allocator = Allocator::default(); |
74 | 77 |
|
75 | 78 | Some(self.lint_path(&allocator, &path, content).map_or(vec![], |errors| { |
76 | | - let mut diagnostics: Vec<DiagnosticReport> = errors |
77 | | - .iter() |
78 | | - .map(|e| message_with_position_to_lsp_diagnostic_report(e, uri)) |
79 | | - .collect(); |
80 | | - |
81 | | - // a diagnostics connected from related_info to original diagnostic |
82 | | - let mut inverted_diagnostics = vec![]; |
83 | | - for d in &diagnostics { |
84 | | - let Some(related_info) = &d.diagnostic.related_information else { |
| 79 | + Self::messages_with_position_to_diagnostics_report(&errors, uri) |
| 80 | + })) |
| 81 | + } |
| 82 | + |
| 83 | + pub fn run_all(&self) -> Vec<(Uri, Vec<DiagnosticReport>)> { |
| 84 | + let allocator = Allocator::default(); |
| 85 | + let mut diagnostics = Vec::new(); |
| 86 | + |
| 87 | + for (path, messages) in self.lint_all_files(&allocator) { |
| 88 | + let uri = path_to_uri(&path); |
| 89 | + diagnostics.push(( |
| 90 | + uri.clone(), |
| 91 | + Self::messages_with_position_to_diagnostics_report(&messages, &uri), |
| 92 | + )); |
| 93 | + } |
| 94 | + |
| 95 | + diagnostics |
| 96 | + } |
| 97 | + |
| 98 | + fn messages_with_position_to_diagnostics_report( |
| 99 | + messages: &[MessageWithPosition<'_>], |
| 100 | + uri: &Uri, |
| 101 | + ) -> Vec<DiagnosticReport> { |
| 102 | + let mut diagnostics: Vec<DiagnosticReport> = messages |
| 103 | + .iter() |
| 104 | + .map(|e| message_with_position_to_lsp_diagnostic_report(e, uri)) |
| 105 | + .collect(); |
| 106 | + |
| 107 | + // a diagnostics connected from related_info to original diagnostic |
| 108 | + let mut inverted_diagnostics = vec![]; |
| 109 | + for d in &diagnostics { |
| 110 | + let Some(related_info) = &d.diagnostic.related_information else { |
| 111 | + continue; |
| 112 | + }; |
| 113 | + let related_information = Some(vec![DiagnosticRelatedInformation { |
| 114 | + location: lsp_types::Location { uri: uri.clone(), range: d.diagnostic.range }, |
| 115 | + message: "original diagnostic".to_string(), |
| 116 | + }]); |
| 117 | + for r in related_info { |
| 118 | + if r.location.range == d.diagnostic.range { |
85 | 119 | continue; |
86 | | - }; |
87 | | - let related_information = Some(vec![DiagnosticRelatedInformation { |
88 | | - location: lsp_types::Location { uri: uri.clone(), range: d.diagnostic.range }, |
89 | | - message: "original diagnostic".to_string(), |
90 | | - }]); |
91 | | - for r in related_info { |
92 | | - if r.location.range == d.diagnostic.range { |
93 | | - continue; |
94 | | - } |
95 | | - // If there is no message content for this span, then don't produce an additional diagnostic |
96 | | - // which also has no content. This prevents issues where editors expect diagnostics to have messages. |
97 | | - if r.message.is_empty() { |
98 | | - continue; |
99 | | - } |
100 | | - inverted_diagnostics.push(DiagnosticReport { |
101 | | - diagnostic: lsp_types::Diagnostic { |
102 | | - range: r.location.range, |
103 | | - severity: Some(DiagnosticSeverity::HINT), |
104 | | - code: None, |
105 | | - message: r.message.clone(), |
106 | | - source: d.diagnostic.source.clone(), |
107 | | - code_description: None, |
108 | | - related_information: related_information.clone(), |
109 | | - tags: None, |
110 | | - data: None, |
111 | | - }, |
112 | | - fixed_content: PossibleFixContent::None, |
113 | | - }); |
114 | 120 | } |
| 121 | + // If there is no message content for this span, then don't produce an additional diagnostic |
| 122 | + // which also has no content. This prevents issues where editors expect diagnostics to have messages. |
| 123 | + if r.message.is_empty() { |
| 124 | + continue; |
| 125 | + } |
| 126 | + inverted_diagnostics.push(DiagnosticReport { |
| 127 | + diagnostic: lsp_types::Diagnostic { |
| 128 | + range: r.location.range, |
| 129 | + severity: Some(DiagnosticSeverity::HINT), |
| 130 | + code: None, |
| 131 | + message: r.message.clone(), |
| 132 | + source: d.diagnostic.source.clone(), |
| 133 | + code_description: None, |
| 134 | + related_information: related_information.clone(), |
| 135 | + tags: None, |
| 136 | + data: None, |
| 137 | + }, |
| 138 | + fixed_content: PossibleFixContent::None, |
| 139 | + }); |
115 | 140 | } |
116 | | - diagnostics.append(&mut inverted_diagnostics); |
117 | | - diagnostics |
118 | | - })) |
| 141 | + } |
| 142 | + diagnostics.append(&mut inverted_diagnostics); |
| 143 | + diagnostics |
119 | 144 | } |
120 | 145 |
|
121 | 146 | fn lint_path<'a>( |
@@ -146,6 +171,25 @@ impl IsolatedLintHandler { |
146 | 171 | result.remove(path) |
147 | 172 | } |
148 | 173 |
|
| 174 | + fn lint_all_files<'a>( |
| 175 | + &self, |
| 176 | + allocator: &'a Allocator, |
| 177 | + ) -> FxHashMap<PathBuf, Vec<MessageWithPosition<'a>>> { |
| 178 | + let walker = FileWalker::new( |
| 179 | + &[self.options.root_path.clone()], |
| 180 | + &FileWalkerOptions { no_ignore: false, symlinks: false, ignore_path: OsString::new() }, |
| 181 | + None, |
| 182 | + ); |
| 183 | + |
| 184 | + let lint_service_options = |
| 185 | + LintServiceOptions::new(self.options.root_path.clone(), walker.paths()) |
| 186 | + .with_cross_module(self.options.use_cross_module); |
| 187 | + |
| 188 | + let mut lint_service = LintService::new(&self.linter, lint_service_options); |
| 189 | + |
| 190 | + lint_service.run_source(allocator) |
| 191 | + } |
| 192 | + |
149 | 193 | fn should_lint_path(path: &Path) -> bool { |
150 | 194 | static WANTED_EXTENSIONS: OnceLock<FxHashSet<&'static str>> = OnceLock::new(); |
151 | 195 | let wanted_exts = |
|
0 commit comments