mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-20 14:25:00 +00:00
perf(linter): share one source per file across type-aware diagnostics (#27462)
`DiagnosticHandler::send_diagnostic` in `crates/oxc_linter/src/tsgolint.rs` copied the file's whole source text into every type-aware diagnostic it sent: `get_source_text(path).to_string()`, then `NamedSource::new(.., source_text.to_owned())` in `wrap_diagnostics`. Each copy lives as long as its diagnostic, so the oxlint process grows with diagnostics × file size. This builds one `Arc<NamedSource>` per file (new `DiagnosticService::named_source`, which `wrap_diagnostics` now uses too) and shares it across that file's diagnostics. Output is unchanged. vscode `src` (10,201 files) with every type-aware rule enabled, `oxlint --type-aware` with oxlint-tsgolint 7.0.2003, 316k diagnostics, release build on an 18-core Mac, two runs each: | | before | after | |---|---:|---:| | oxlint process, peak RSS | 30.5 / 31.8 GiB | 0.7 / 0.7 GiB | | oxlint + tsgolint, peak RSS | 39.5 / 41.0 GiB | 10.0 / 10.0 GiB | | wall | 24.0 / 18.8 s | 18.1 / 15.8 s | The build measured here also carries the in-process tsrs backend from the draft below, which is inactive when oxlint runs the Go tsgolint. On a 27k-file project with 21 diagnostics, time and memory do not change. Checked: the type-aware fixture tests (`cargo test -p oxlint -- tsgolint`, 27) pass unchanged, as do `oxc_diagnostics` and `oxc_linter`'s unit tests; clippy is clean with warnings denied. Also the second commit of: - https://github.com/oxc-project/oxc/pull/27461 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
316bf847f8
commit
8c57c5ecce
2 files changed
+34
-14
No files matched your search
@@ -116,6 +116,21 @@ impl DiagnosticService {
|
||||
source_text: &str,
|
||||
diagnostics: Vec<OxcDiagnostic>,
|
||||
) -> Vec<Error> {
|
||||
let source = Self::named_source(cwd, path, source_text);
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.with_source_code(Arc::clone(&source)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The source code and display path [`wrap_diagnostics`](DiagnosticService::wrap_diagnostics) attaches. Each
|
||||
/// call copies `source_text`, so a caller that wraps one file's diagnostics a few at a time should build this once
|
||||
/// per file and share it with [`OxcDiagnostic::with_source_code`].
|
||||
pub fn named_source<C: AsRef<Path>, P: AsRef<Path>>(
|
||||
cwd: C,
|
||||
path: P,
|
||||
source_text: &str,
|
||||
) -> Arc<NamedSource<String>> {
|
||||
// TODO: This causes snapshots to fail when running tests through a JetBrains terminal.
|
||||
let is_jetbrains =
|
||||
std::env::var("TERMINAL_EMULATOR").is_ok_and(|x| x.eq("JetBrains-JediTerm"));
|
||||
@@ -129,11 +144,7 @@ impl DiagnosticService {
|
||||
normalized_path.to_string()
|
||||
});
|
||||
|
||||
let source = Arc::new(NamedSource::new(path_display, source_text.to_owned()));
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.with_source_code(Arc::clone(&source)))
|
||||
.collect()
|
||||
Arc::new(NamedSource::new(path_display, source_text.to_owned()))
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
|
||||
@@ -13,7 +13,7 @@ use oxc_allocator::Allocator;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use oxc_diagnostics::{DiagnosticSender, DiagnosticService, OxcDiagnostic, Severity};
|
||||
use oxc_diagnostics::{DiagnosticSender, DiagnosticService, NamedSource, OxcDiagnostic, Severity};
|
||||
use oxc_span::{SourceType, Span};
|
||||
|
||||
use super::{AllowWarnDeny, ConfigStore, DisableDirectives, ResolvedLinterState, read_to_string};
|
||||
@@ -1063,6 +1063,8 @@ struct DiagnosticHandler {
|
||||
silent: bool,
|
||||
should_fix: bool,
|
||||
source_text_cache: SourceTextCache,
|
||||
/// One source per file, shared by every diagnostic of that file that is sent as it arrives.
|
||||
named_sources: FxHashMap<PathBuf, Arc<NamedSource<String>>>,
|
||||
error_sender: DiagnosticSender,
|
||||
/// Messages requiring fixes, grouped by file path: messages.
|
||||
messages_requiring_fixes: FxHashMap<PathBuf, Vec<Message>>,
|
||||
@@ -1076,6 +1078,7 @@ impl DiagnosticHandler {
|
||||
silent,
|
||||
should_fix,
|
||||
source_text_cache: SourceTextCache::default(),
|
||||
named_sources: FxHashMap::default(),
|
||||
error_sender,
|
||||
messages_requiring_fixes: FxHashMap::default(),
|
||||
messages_not_requiring_fixes: FxHashMap::default(),
|
||||
@@ -1145,19 +1148,25 @@ impl DiagnosticHandler {
|
||||
oxc_diagnostic: OxcDiagnostic,
|
||||
severity: AllowWarnDeny,
|
||||
) {
|
||||
let source_text = self.get_source_text(path).to_string();
|
||||
let oxc_diagnostic = oxc_diagnostic.with_severity(if severity == AllowWarnDeny::Deny {
|
||||
Severity::Error
|
||||
} else {
|
||||
Severity::Warning
|
||||
});
|
||||
let diagnostics = DiagnosticService::wrap_diagnostics(
|
||||
&self.cwd,
|
||||
path,
|
||||
&source_text,
|
||||
vec![oxc_diagnostic],
|
||||
);
|
||||
self.error_sender.send(diagnostics).expect("Failed to send diagnostics");
|
||||
let source = self.named_source(path);
|
||||
self.error_sender
|
||||
.send(vec![oxc_diagnostic.with_source_code(source)])
|
||||
.expect("Failed to send diagnostics");
|
||||
}
|
||||
|
||||
fn named_source(&mut self, path: &Path) -> Arc<NamedSource<String>> {
|
||||
if let Some(source) = self.named_sources.get(path) {
|
||||
return Arc::clone(source);
|
||||
}
|
||||
let cwd = self.cwd.clone();
|
||||
let source = DiagnosticService::named_source(cwd, path, self.get_source_text(path));
|
||||
self.named_sources.insert(path.to_path_buf(), Arc::clone(&source));
|
||||
source
|
||||
}
|
||||
|
||||
/// Consume the handler and return collected messages requiring fixes.
|
||||
|
||||
Reference in new issue
Block a user