-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary Implement support for RDJson output for `ruff check`, as requested in #8655. ## Test Plan Tested using a snapshot test. Same approach as for e.g. the JSON output formatter. ## Additional info I tried to keep the implementation close to the JSON implementation. I had to deviate a bit to make the `suggestions` key work: If there are no suggestions, then setting `suggestions` to `null` is invalid according to the JSONSchema. Therefore, I opted for a slightly more complex implementation, that skips the `suggestions` key entirely if there are no fixes available for the given diagnostic. Maybe it would have been easier to set `"suggestions": []`, but I ended up doing it this way. I didn't consider notebooks, as I _think_ that RDJson doesn't work with notebooks. This should be confirmed, and if so, there should be some form of warning or error emitted when trying to output diagnostics for a notebook. I also didn't consider `ruff format`, as this comment: #8655 (comment) suggests that that wouldn't be compatible. I'm new to Rust, any feedback is appreciated. 🙂 I implemented this in order to have a productive rainy saturday afternoon, I'm not knowledgeable about RDJson beyond the sources linked in the issue.
- Loading branch information
Showing
7 changed files
with
252 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
use std::io::Write; | ||
|
||
use serde::ser::SerializeSeq; | ||
use serde::{Serialize, Serializer}; | ||
use serde_json::{json, Value}; | ||
|
||
use ruff_diagnostics::Edit; | ||
use ruff_source_file::SourceCode; | ||
use ruff_text_size::Ranged; | ||
|
||
use crate::message::{Emitter, EmitterContext, Message, SourceLocation}; | ||
use crate::registry::AsRule; | ||
|
||
#[derive(Default)] | ||
pub struct RdjsonEmitter; | ||
|
||
impl Emitter for RdjsonEmitter { | ||
fn emit( | ||
&mut self, | ||
writer: &mut dyn Write, | ||
messages: &[Message], | ||
_context: &EmitterContext, | ||
) -> anyhow::Result<()> { | ||
serde_json::to_writer_pretty( | ||
writer, | ||
&json!({ | ||
"source": { | ||
"name": "ruff", | ||
"url": "https://docs.astral.sh/ruff", | ||
}, | ||
"severity": "warning", | ||
"diagnostics": &ExpandedMessages{ messages } | ||
}), | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
struct ExpandedMessages<'a> { | ||
messages: &'a [Message], | ||
} | ||
|
||
impl Serialize for ExpandedMessages<'_> { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
let mut s = serializer.serialize_seq(Some(self.messages.len()))?; | ||
|
||
for message in self.messages { | ||
let value = message_to_rdjson_value(message); | ||
s.serialize_element(&value)?; | ||
} | ||
|
||
s.end() | ||
} | ||
} | ||
|
||
fn message_to_rdjson_value(message: &Message) -> Value { | ||
let source_code = message.file.to_source_code(); | ||
|
||
let start_location = source_code.source_location(message.start()); | ||
let end_location = source_code.source_location(message.end()); | ||
|
||
if let Some(fix) = message.fix.as_ref() { | ||
json!({ | ||
"message": message.kind.body, | ||
"location": { | ||
"path": message.filename(), | ||
"range": rdjson_range(&start_location, &end_location), | ||
}, | ||
"code": { | ||
"value": message.kind.rule().noqa_code().to_string(), | ||
"url": message.kind.rule().url(), | ||
}, | ||
"suggestions": rdjson_suggestions(fix.edits(), &source_code), | ||
}) | ||
} else { | ||
json!({ | ||
"message": message.kind.body, | ||
"location": { | ||
"path": message.filename(), | ||
"range": rdjson_range(&start_location, &end_location), | ||
}, | ||
"code": { | ||
"value": message.kind.rule().noqa_code().to_string(), | ||
"url": message.kind.rule().url(), | ||
}, | ||
}) | ||
} | ||
} | ||
|
||
fn rdjson_suggestions(edits: &[Edit], source_code: &SourceCode) -> Value { | ||
Value::Array( | ||
edits | ||
.iter() | ||
.map(|edit| { | ||
let location = source_code.source_location(edit.start()); | ||
let end_location = source_code.source_location(edit.end()); | ||
|
||
json!({ | ||
"range": rdjson_range(&location, &end_location), | ||
"text": edit.content().unwrap_or_default(), | ||
}) | ||
}) | ||
.collect(), | ||
) | ||
} | ||
|
||
fn rdjson_range(start: &SourceLocation, end: &SourceLocation) -> Value { | ||
json!({ | ||
"start": { | ||
"line": start.row, | ||
"column": start.column, | ||
}, | ||
"end": { | ||
"line": end.row, | ||
"column": end.column, | ||
}, | ||
}) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use insta::assert_snapshot; | ||
|
||
use crate::message::tests::{capture_emitter_output, create_messages}; | ||
use crate::message::RdjsonEmitter; | ||
|
||
#[test] | ||
fn output() { | ||
let mut emitter = RdjsonEmitter; | ||
let content = capture_emitter_output(&mut emitter, &create_messages()); | ||
|
||
assert_snapshot!(content); | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
crates/ruff_linter/src/message/snapshots/ruff_linter__message__rdjson__tests__output.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
--- | ||
source: crates/ruff_linter/src/message/rdjson.rs | ||
expression: content | ||
--- | ||
{ | ||
"diagnostics": [ | ||
{ | ||
"code": { | ||
"url": "https://docs.astral.sh/ruff/rules/unused-import", | ||
"value": "F401" | ||
}, | ||
"location": { | ||
"path": "fib.py", | ||
"range": { | ||
"end": { | ||
"column": 10, | ||
"line": 1 | ||
}, | ||
"start": { | ||
"column": 8, | ||
"line": 1 | ||
} | ||
} | ||
}, | ||
"message": "`os` imported but unused", | ||
"suggestions": [ | ||
{ | ||
"range": { | ||
"end": { | ||
"column": 1, | ||
"line": 2 | ||
}, | ||
"start": { | ||
"column": 1, | ||
"line": 1 | ||
} | ||
}, | ||
"text": "" | ||
} | ||
] | ||
}, | ||
{ | ||
"code": { | ||
"url": "https://docs.astral.sh/ruff/rules/unused-variable", | ||
"value": "F841" | ||
}, | ||
"location": { | ||
"path": "fib.py", | ||
"range": { | ||
"end": { | ||
"column": 6, | ||
"line": 6 | ||
}, | ||
"start": { | ||
"column": 5, | ||
"line": 6 | ||
} | ||
} | ||
}, | ||
"message": "Local variable `x` is assigned to but never used", | ||
"suggestions": [ | ||
{ | ||
"range": { | ||
"end": { | ||
"column": 10, | ||
"line": 6 | ||
}, | ||
"start": { | ||
"column": 5, | ||
"line": 6 | ||
} | ||
}, | ||
"text": "" | ||
} | ||
] | ||
}, | ||
{ | ||
"code": { | ||
"url": "https://docs.astral.sh/ruff/rules/undefined-name", | ||
"value": "F821" | ||
}, | ||
"location": { | ||
"path": "undef.py", | ||
"range": { | ||
"end": { | ||
"column": 5, | ||
"line": 1 | ||
}, | ||
"start": { | ||
"column": 4, | ||
"line": 1 | ||
} | ||
} | ||
}, | ||
"message": "Undefined name `a`" | ||
} | ||
], | ||
"severity": "warning", | ||
"source": { | ||
"name": "ruff", | ||
"url": "https://docs.astral.sh/ruff" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.