diff --git a/crates/rustfix/src/diagnostics.rs b/crates/rustfix/src/diagnostics.rs index f745b0b2a92..ad1899b2cc8 100644 --- a/crates/rustfix/src/diagnostics.rs +++ b/crates/rustfix/src/diagnostics.rs @@ -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. @@ -14,12 +17,11 @@ pub struct Diagnostic { pub spans: Vec, /// Associated diagnostic messages. pub children: Vec, - /// 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, } +/// Span information of a diagnostic item. #[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)] pub struct DiagnosticSpan { pub file_name: String, @@ -39,23 +41,43 @@ pub struct DiagnosticSpan { /// Label that should be placed at this location (if any) label: Option, /// 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, + /// If the suggestion is approximate pub suggestion_applicability: Option, /// Macro invocations that created the code at this span, if any. expansion: Option>, } +/// 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, @@ -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 @@ -80,6 +103,9 @@ struct DiagnosticSpanMacroExpansion { def_site_span: Option, } +/// 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. diff --git a/crates/rustfix/src/lib.rs b/crates/rustfix/src/lib.rs index 50c10f2b339..789ca6534e6 100644 --- a/crates/rustfix/src/lib.rs +++ b/crates/rustfix/src/lib.rs @@ -1,14 +1,15 @@ //! 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: //! @@ -16,7 +17,7 @@ //! 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; @@ -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( input: &str, only: &HashSet, @@ -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, pub solutions: Vec, } +/// 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, } +/// Represents code that will get replaced. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Snippet { pub file_name: String, @@ -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 { // unindent the snippet let indent = span @@ -168,6 +185,7 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option { }) } +/// Converts a [`DiagnosticSpan`] into a [`Replacement`]. fn collect_span(span: &DiagnosticSpan) -> Option { let snippet = parse_snippet(span)?; let replacement = span.suggested_replacement.clone()?; @@ -177,6 +195,9 @@ fn collect_span(span: &DiagnosticSpan) -> Option { }) } +/// 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( diagnostic: &Diagnostic, only: &HashSet, @@ -237,17 +258,26 @@ pub fn collect_suggestions( } } +/// 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 { @@ -258,11 +288,13 @@ impl CodeFix { Ok(()) } + /// Gets the result of the "fixed" code. pub fn finish(&self) -> Result { Ok(String::from_utf8(self.data.to_vec())?) } } +/// Applies multiple `suggestions` to the given `code`. pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result { let mut fix = CodeFix::new(code); for suggestion in suggestions.iter().rev() { diff --git a/crates/rustfix/src/replace.rs b/crates/rustfix/src/replace.rs index 01a781f7c09..69aa3fe5816 100644 --- a/crates/rustfix/src/replace.rs +++ b/crates/rustfix/src/replace.rs @@ -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]>), } @@ -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, + /// [`Span`]s covering the full range of the original data. parts: Vec, } diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index a4049db0b7e..b4114017257 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -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