port create_notebook_diagnostics and corresponding json test

This commit is contained in:
Brent Westbrook 2025-07-07 16:16:35 -04:00
parent f7511b44af
commit 339cfd30ce
3 changed files with 263 additions and 1 deletions

View file

@ -834,6 +834,9 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
#[cfg(test)]
mod tests {
use ruff_diagnostics::{Edit, Fix};
use rustc_hash::FxHashMap;
use crate::diagnostic::{Annotation, DiagnosticId, SecondaryCode, Severity, Span};
use crate::files::system_path_to_file;
use crate::system::{DbWithWritableSystem, SystemPath};
@ -2234,6 +2237,7 @@ watermelon
pub(crate) struct TestEnvironment {
db: TestDb,
config: DisplayDiagnosticConfig,
notebook_indexes: FxHashMap<String, NotebookIndex>,
}
impl TestEnvironment {
@ -2244,6 +2248,7 @@ watermelon
TestEnvironment {
db: TestDb::new(),
config: DisplayDiagnosticConfig::default(),
notebook_indexes: FxHashMap::default(),
}
}
@ -2272,6 +2277,10 @@ watermelon
self.db.write_file(path, contents).unwrap();
}
fn add_notebook_index(&mut self, name: String, index: NotebookIndex) {
self.notebook_indexes.insert(name, index);
}
/// Conveniently create a `Span` that points into a file in this
/// environment.
///
@ -2368,7 +2377,33 @@ watermelon
///
/// (This will set the "printed" flag on `Diagnostic`.)
pub(crate) fn render_diagnostics(&self, diagnostics: &[Diagnostic]) -> String {
DisplayDiagnostics::new(&self.db, &self.config, diagnostics).to_string()
DisplayDiagnostics::new(self, &self.config, diagnostics).to_string()
}
}
// This mostly defers to `self.db`, but we want to control `notebook_index` a bit more closely.
// See `create_notebook_diagnostics` for an example.
impl FileResolver for TestEnvironment {
fn path(&self, file: File) -> &str {
self.db.path(file)
}
fn input(&self, file: File) -> Input {
self.db.input(file)
}
fn notebook_index(&self, file: &UnifiedFile) -> Option<NotebookIndex> {
match file {
UnifiedFile::Ty(file) => self.notebook_indexes.get(self.db.path(*file)).cloned(),
UnifiedFile::Ruff(_) => unimplemented!(),
}
}
fn is_notebook(&self, file: &UnifiedFile) -> bool {
match file {
UnifiedFile::Ty(file) => self.notebook_indexes.contains_key(self.db.path(*file)),
UnifiedFile::Ruff(_) => unimplemented!(),
}
}
}
@ -2441,6 +2476,18 @@ watermelon
.set_secondary_code(SecondaryCode::new(secondary_code.to_string()));
self
}
/// Set the fix on the diagnostic.
fn fix(mut self, fix: Fix) -> DiagnosticBuilder<'e> {
self.diag.set_fix(fix);
self
}
/// Set the fix on the diagnostic.
fn noqa_offset(mut self, noqa_offset: TextSize) -> DiagnosticBuilder<'e> {
self.diag.set_noqa_offset(noqa_offset);
self
}
}
/// A helper builder for tersely populating a `SubDiagnostic`.
@ -2610,4 +2657,103 @@ if call(foo
(env, diagnostics)
}
pub(crate) fn create_notebook_diagnostics(
format: DiagnosticFormat,
) -> (TestEnvironment, Vec<Diagnostic>) {
let mut env = TestEnvironment::new();
// We use `.py` here to avoid having to provide a real notebook (i.e. a blob of JSON). The
// manual `NotebookIndex` constructed below will still cause the rendering to treat it like
// a notebook.
env.add(
"notebook.py",
r"# cell 1
import os
# cell 2
import math
print('hello world')
# cell 3
def foo():
print()
x = 1
",
);
env.format(format);
let diagnostics = vec![
env.builder("unused-import", Severity::Error, "`os` imported but unused")
.primary("notebook.py", "2:7", "2:9", "Remove unused import: `os`")
.secondary_code("F401")
.fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(9),
TextSize::from(19),
))))
.noqa_offset(TextSize::from(16))
.build(),
env.builder(
"unused-import",
Severity::Error,
"`math` imported but unused",
)
.primary("notebook.py", "4:7", "4:11", "Remove unused import: `math`")
.secondary_code("F401")
.fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(28),
TextSize::from(40),
))))
.noqa_offset(TextSize::from(35))
.build(),
env.builder(
"unused-variable",
Severity::Error,
"Local variable `x` is assigned to but never used",
)
.primary(
"notebook.py",
"10:4",
"10:5",
"Remove assignment to unused variable `x`",
)
.secondary_code("F841")
.fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(94),
TextSize::from(104),
))))
.noqa_offset(TextSize::from(98))
.build(),
];
env.add_notebook_index(
"notebook.py".to_string(),
NotebookIndex::new(
vec![
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(2),
],
vec![
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(3),
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(3),
],
),
);
(env, diagnostics)
}
}

View file

@ -164,3 +164,14 @@ impl Serialize for ExpandedEdits<'_> {
s.end()
}
}
#[cfg(test)]
mod tests {
use crate::diagnostic::{DiagnosticFormat, render::tests::create_notebook_diagnostics};
#[test]
fn notebook_output() {
let (env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Json);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
}
}

View file

@ -0,0 +1,105 @@
---
source: crates/ruff_db/src/diagnostic/render/json.rs
expression: env.render_diagnostics(&diagnostics)
---
[
{
"cell": 1,
"code": "F401",
"end_location": {
"column": 10,
"row": 2
},
"filename": "notebook.py",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 10,
"row": 2
},
"location": {
"column": 1,
"row": 2
}
}
],
"message": "Remove unused import: `os`"
},
"location": {
"column": 8,
"row": 2
},
"message": "`os` imported but unused",
"noqa_row": 2,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
},
{
"cell": 2,
"code": "F401",
"end_location": {
"column": 12,
"row": 2
},
"filename": "notebook.py",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 3
},
"location": {
"column": 1,
"row": 2
}
}
],
"message": "Remove unused import: `math`"
},
"location": {
"column": 8,
"row": 2
},
"message": "`math` imported but unused",
"noqa_row": 2,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
},
{
"cell": 3,
"code": "F841",
"end_location": {
"column": 6,
"row": 4
},
"filename": "notebook.py",
"fix": {
"applicability": "unsafe",
"edits": [
{
"content": "",
"end_location": {
"column": 10,
"row": 4
},
"location": {
"column": 1,
"row": 4
}
}
],
"message": "Remove assignment to unused variable `x`"
},
"location": {
"column": 5,
"row": 4
},
"message": "Local variable `x` is assigned to but never used",
"noqa_row": 4,
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
}
]