Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add doc comments for rustfix #13048

Merged
merged 2 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions crates/rustfix/src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Rustc Diagnostic JSON Output
//! Rustc Diagnostic JSON Output.
//!
//! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/de78655bca47cac8e783dbb563e7e5c25c1fae40/src/libsyntax/json.rs)
//! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/4fd68eb47bad1c121417ac4450b2f0456150db86/compiler/rustc_errors/src/json.rs).
//!
//! For examples of the JSON output, see JSON fixture files under `tests/` directory.

use serde::Deserialize;

/// The root diagnostic JSON output emitted by the compiler.
#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)]
pub struct Diagnostic {
/// The primary error message.
Expand All @@ -14,12 +17,11 @@ pub struct Diagnostic {
pub spans: Vec<DiagnosticSpan>,
/// Associated diagnostic messages.
pub children: Vec<Diagnostic>,
/// The message as rustc would render it. Currently this is only
/// `Some` for "suggestions", but eventually it will include all
/// snippets.
/// The message as rustc would render it.
pub rendered: Option<String>,
}

/// Span information of a diagnostic item.
#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)]
pub struct DiagnosticSpan {
pub file_name: String,
Expand All @@ -39,23 +41,43 @@ pub struct DiagnosticSpan {
/// Label that should be placed at this location (if any)
label: Option<String>,
/// If we are suggesting a replacement, this will contain text
/// that should be sliced in atop this span. You may prefer to
/// load the fully rendered version from the parent `Diagnostic`,
/// however.
/// that should be sliced in atop this span.
pub suggested_replacement: Option<String>,
/// If the suggestion is approximate
pub suggestion_applicability: Option<Applicability>,
/// Macro invocations that created the code at this span, if any.
expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
}

/// Indicates the confidence in the correctness of a suggestion.
///
/// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion
/// to determine whether it should be automatically applied or if the user should be consulted
/// before applying the suggestion.
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
pub enum Applicability {
/// The suggestion is definitely what the user intended, or maintains the exact meaning of the code.
/// This suggestion should be automatically applied.
///
/// In case of multiple `MachineApplicable` suggestions (whether as part of
/// the same `multipart_suggestion` or not), all of them should be
/// automatically applied.
MachineApplicable,
HasPlaceholders,

/// The suggestion may be what the user intended, but it is uncertain. The suggestion should
/// result in valid Rust code if it is applied.
MaybeIncorrect,

/// The suggestion contains placeholders like `(...)` or `{ /* fields */ }`. The suggestion
/// cannot be applied automatically because it will not result in valid Rust code. The user
/// will need to fill in the placeholders.
HasPlaceholders,

/// The applicability of the suggestion is unknown.
Unspecified,
}

/// Span information of a single line.
#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
pub struct DiagnosticSpanLine {
pub text: String,
Expand All @@ -66,6 +88,7 @@ pub struct DiagnosticSpanLine {
pub highlight_end: usize,
}

/// Span information for macro expansions.
#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
struct DiagnosticSpanMacroExpansion {
/// span where macro was applied to generate this code; note that
Expand All @@ -80,6 +103,9 @@ struct DiagnosticSpanMacroExpansion {
def_site_span: Option<DiagnosticSpan>,
}

/// The error code emitted by the compiler. See [Rust error codes index].
///
/// [Rust error codes index]: https://doc.rust-lang.org/error_codes/error-index.html
#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
pub struct DiagnosticCode {
/// The code itself.
Expand Down
42 changes: 37 additions & 5 deletions crates/rustfix/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
//! Library for applying diagnostic suggestions to source code.
//!
//! This is a low-level library. You pass it the JSON output from `rustc`, and
//! you can then use it to apply suggestions to in-memory strings. This
//! library doesn't execute commands, or read or write from the filesystem.
//! This is a low-level library. You pass it the [JSON output] from `rustc`,
//! and you can then use it to apply suggestions to in-memory strings.
//! This library doesn't execute commands, or read or write from the filesystem.
//!
//! If you are looking for the [`cargo fix`] implementation, the core of it is
//! located in [`cargo::ops::fix`].
//!
//! [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
//! [`cargo::ops::fix`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/fix.rs
//! [JSON output]: diagnostics
//!
//! The general outline of how to use this library is:
//!
//! 1. Call `rustc` and collect the JSON data.
//! 2. Pass the json data to [`get_suggestions_from_json`].
//! 3. Create a [`CodeFix`] with the source of a file to modify.
//! 4. Call [`CodeFix::apply`] to apply a change.
//! 5. Write the source back to disk.
//! 5. Call [`CodeFix::finish`] to get the result and write it back to disk.

use std::collections::HashSet;
use std::ops::Range;
Expand All @@ -27,12 +28,20 @@ pub mod diagnostics;
use crate::diagnostics::{Diagnostic, DiagnosticSpan};
mod replace;

/// A filter to control which suggestion should be applied.
#[derive(Debug, Clone, Copy)]
pub enum Filter {
/// For [`diagnostics::Applicability::MachineApplicable`] only.
MachineApplicableOnly,
/// Everything is included. YOLO!
Everything,
}

/// Collects code [`Suggestion`]s from one or more compiler diagnostic lines.
///
/// Fails if any of diagnostic line `input` is not a valid [`Diagnostic`] JSON.
///
/// * `only` --- only diagnostics with code in a set of error codes would be collected.
pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>(
input: &str,
only: &HashSet<String, S>,
Expand Down Expand Up @@ -70,20 +79,24 @@ impl std::fmt::Display for LineRange {
}
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
/// An error/warning and possible solutions for fixing it
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Suggestion {
pub message: String,
pub snippets: Vec<Snippet>,
pub solutions: Vec<Solution>,
}

/// Solution to a diagnostic item.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Solution {
/// The error message of the diagnostic item.
pub message: String,
/// Possible solutions to fix the error.
pub replacements: Vec<Replacement>,
}

/// Represents code that will get replaced.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Snippet {
pub file_name: String,
Expand All @@ -95,12 +108,16 @@ pub struct Snippet {
pub text: (String, String, String),
}

/// Represents a replacement of a `snippet`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Replacement {
/// Code snippet that gets replaced.
pub snippet: Snippet,
/// The replacement of the snippet.
pub replacement: String,
}

/// Parses a [`Snippet`] from a diagnostic span item.
fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
// unindent the snippet
let indent = span
Expand Down Expand Up @@ -168,6 +185,7 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
})
}

/// Converts a [`DiagnosticSpan`] into a [`Replacement`].
fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> {
let snippet = parse_snippet(span)?;
let replacement = span.suggested_replacement.clone()?;
Expand All @@ -177,6 +195,9 @@ fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> {
})
}

/// Collects code [`Suggestion`]s from a single compiler diagnostic line.
///
/// * `only` --- only diagnostics with code in a set of error codes would be collected.
pub fn collect_suggestions<S: ::std::hash::BuildHasher>(
diagnostic: &Diagnostic,
only: &HashSet<String, S>,
Expand Down Expand Up @@ -237,17 +258,26 @@ pub fn collect_suggestions<S: ::std::hash::BuildHasher>(
}
}

/// Represents a code fix. This doesn't write to disks but is only in memory.
///
/// The general way to use this is:
///
/// 1. Feeds the source of a file to [`CodeFix::new`].
/// 2. Calls [`CodeFix::apply`] to apply suggestions to the source code.
/// 3. Calls [`CodeFix::finish`] to get the "fixed" code.
pub struct CodeFix {
data: replace::Data,
}

impl CodeFix {
/// Creates a `CodeFix` with the source of a file to modify.
pub fn new(s: &str) -> CodeFix {
CodeFix {
data: replace::Data::new(s.as_bytes()),
}
}

/// Applies a suggestion to the code.
pub fn apply(&mut self, suggestion: &Suggestion) -> Result<(), Error> {
for sol in &suggestion.solutions {
for r in &sol.replacements {
Expand All @@ -258,11 +288,13 @@ impl CodeFix {
Ok(())
}

/// Gets the result of the "fixed" code.
pub fn finish(&self) -> Result<String, Error> {
Ok(String::from_utf8(self.data.to_vec())?)
}
}

/// Applies multiple `suggestions` to the given `code`.
pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result<String, Error> {
let mut fix = CodeFix::new(code);
for suggestion in suggestions.iter().rev() {
Expand Down
8 changes: 8 additions & 0 deletions crates/rustfix/src/replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
use anyhow::{anyhow, ensure, Error};
use std::rc::Rc;

/// Indicates the change state of a [`Span`].
#[derive(Debug, Clone, PartialEq, Eq)]
enum State {
/// The initial state. No change applied.
Initial,
/// Has been replaced.
Replaced(Rc<[u8]>),
/// Has been inserted.
Inserted(Rc<[u8]>),
}

Expand All @@ -18,19 +22,23 @@ impl State {
}
}

/// Span with a change [`State`].
#[derive(Debug, Clone, PartialEq, Eq)]
struct Span {
/// Start of this span in parent data
start: usize,
/// up to end excluding
end: usize,
/// Whether the span is inserted, replaced or still fresh.
data: State,
}

/// A container that allows easily replacing chunks of its data
#[derive(Debug, Clone, Default)]
pub struct Data {
/// Original data.
original: Vec<u8>,
/// [`Span`]s covering the full range of the original data.
parts: Vec<Span>,
}

Expand Down
5 changes: 5 additions & 0 deletions src/cargo/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
//! This is not directly depended upon with a `path` dependency; cargo uses the version from crates.io.
//! It is intended to be versioned and published independently of Rust's release system.
//! Whenever a change needs to be made, bump the version in Cargo.toml and `cargo publish` it manually, and then update cargo's `Cargo.toml` to depend on the new version.
//! - [`rustfix`](https://crates.io/crates/rustfix)
//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/rustfix)):
//! This defines structures that represent fix suggestions from rustc,
//! as well as generates "fixed" code from suggestions.
//! Operations in `rustfix` are all in memory and won't write to disks.
//! - [`cargo-test-support`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-test-support)
//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_support/index.html)):
//! This contains a variety of code to support writing tests
Expand Down