From 745464fb69d048de348b31b0b28e265b9e9fe0a8 Mon Sep 17 00:00:00 2001 From: l3ops Date: Wed, 19 Oct 2022 15:30:07 +0200 Subject: [PATCH] docs(rome_diagnostics): add a contributor documentation file for diagnostics (#3401) Co-authored-by: Emanuele Stoppa --- crates/rome_diagnostics/CONTRIBUTING.md | 140 ++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 crates/rome_diagnostics/CONTRIBUTING.md diff --git a/crates/rome_diagnostics/CONTRIBUTING.md b/crates/rome_diagnostics/CONTRIBUTING.md new file mode 100644 index 00000000000..d8fe26a36b5 --- /dev/null +++ b/crates/rome_diagnostics/CONTRIBUTING.md @@ -0,0 +1,140 @@ +# Writing Diagnostics + +The `rome_diagnostics` crate implements "Diagnostics", the generic concept of +how the various errors and issues that can be raised in the Rome codebase, +and displayed to the final user. This guide is aimed at +contributors to the Rome codebase to help writing diagnostics, both at the +technical level, and as general best practices to make diagnostics easier to +understand for the final user. + +## What is a Diagnostic + +At the lowest level, `Diagnostic` is a Rust trait implemented by various types +across the codebase that each represent a specific kind of diagnostic. All +these types have the following properties in common, as enforced by the trait: + +- A `category`, the unique string of the error code associated with this +diagnostic type such as `lint/correctness/noArguments`, `args/invalid` or +`format/disabled` +- A `severity` defining how important the issue described by the diagnostic is +to the user. The severity of a diagnostic can be `Fatal`, `Error`, `Warning`, +`Information` or `Hint` +- A `description` text explaining the diagnostic to the user. This is what will +be displayed in contexts that do not support rich markup, such as embedded +error popovers in editors. Since the description is shown in contexts where the +advices cannot be rendered, it should provide as much information as possible about the +error. +- A `message`, a piece of rich markup that will be displayed at the top of the +diagnostic advices. Contrary to the description, the message is displayed along +with the other advices of the diagnostic and should only provide a short and +clear explanation of the error, while advices will be used to provide +additional information with the appropriate context. +- A set of `advices`, the main building blocks used to provide the user with +rich information about the diagnostic. Depending on the use case, advices can be +used to log pieces of markup, lists of items, annotated code frames, text diffs, +Rust backtraces, command lines, or groups of more advices. +- An optional set of `verbose_advices` the user can optionally enable (at the +moment these are always printed but they will eventually have to be enabled, +for instance by passing a `--verbose` flag to the CLI). Verbose advices are +printed in a separate section below the main advices and can be used to provide +further information about a given diagnostic (for instance a fixable linter +diagnostic may print a verbose advice instructing the user to run +`rome check --apply` to automatically apply the fix). +- A `location` describing where the error happened. It can be a path to a file +on the file system, a command line argument, or an arbitrary memory buffer. It may +optionally specify a specific range within the text content of this resource, +as well as embed said text content to faciliate it's retrieval when displaying +code frames in the diagnostic. Conceptually the `location` points to a +"resource" this error originated from. +- `tags` conveying additional informations about the diagnostic: if the +diagnostic has information on how it can be fixed, if it resulted from an +internal error in Rome and was not directly caused by the user, if it is being +emitted to warn the user about unused or deprecated code. +- An optional `source`, another diagnostic that details the low-level reason +why this diagnostic was emitted: for instance a diagnostic reporting a failed +request to a remote server may have a deserialization error for the server +response as its cause. Conceptually the `source` points to a `Diagnostic` this +error originated from, so while they are similar in purpose (explaining where +an error comes from to the user) the `location` and `source` properties are not +incompatible: taking from the above example of a "failed request" diagnostic, +it may have a "deserialization error" diagnostic as a `source`, and the body +of the invalid response as its `location`. + +## How to implement Diagnostic + +In theory a diagnostic is created by implementing the `Diagnostic` trait on a +type. In practice since there's a lot of methods to implement, the +`rome_diagnostics` crate exposes a `derive` procedural macro to make +implementing the trait easier: + +```rust +// The Diagnostic trait requires Debug to be implemented +#[derive(Debug, Diagnostic)] +// The category, severity, description, message, location and tags can be +// specified statically on the type itself using the #[diagnostic] attribute +#[diagnostic(severity = Warning, category = "internalError/fs")] +struct UnhandledDiagnostic { + // All the diagnostic properties can also be derived from fields of the + // struct using the corresponding attribute + // A single field may have multiple attributes, however most attributes can + // only be specified once either statically on the whole struct or on a + // single field. The only exception to this is #[advice] (and + // #[verbose_advice]), since all advices will be recorded into the + // diagnostic in the same order they are declared in the struct + #[message] + #[description] + #[advice] + file_kind: UnhandledKind, + // For the location, it's possible to specify a sub-property between + // `resource`, `span` and `source_code` + #[location(resource)] + file_id: FileId, +} +``` + +This should be enough to define most of the properties of a diagnostic, but +advices are a bit more complex. Fields that have the `#[advice]` or +`#[verbose_advice]` attribute are expected to implement the `Advices` trait. +This trait allows arbitrary types to record advices on the diagnostic that +contains them: + +```rust +impl Advices for UnhandledKind { + fn record(&self, visitor: &mut dyn Visit) -> io::Result<()> { + visitor.record_log(LogCategory::Info, self.advice_message()) + } +} +``` + +In order to add advices to a diagnostic, you can either use some of the helper +types in the `rome_diagnostics::v2` module (`CodeFrameAdvice`, `CommandAdvice`, +`DiffAdvice`, `LogAdvice`), or implement the `Advices` trait yourself if you +want more control over how the advices are recorded (this can be useful to +optimize the performance of a diagnostic by reducing how much memory it +allocates). + +The category may also require some special care if you're declaring a new one, +since all diagnostic categories have to be statically registered you'll need to +add it to `crates/rome_diagnostics_categories/src/categories.rs` + +## Writing a Diagnostic + +Diagnostics are at the core of the experience of the Rome toolchain for the +users, and providing high quality diagnostics is crucial to making the usage of +Rome as frictionless as possible even when errors happen. What follows is a +list of best practice for writing good diagnostics: + +- The Diagnostic should follow the (Technical Principles)[https://rome.tools/#technical] +of the Rome project +- A diagnostic should not simply state that something went wrong, it should +explain why it went wrong. Add explanations in log advices, and show hyperlinks +to relevant documentation pages in case the user wants to know more. +- If possible, a diagnostic should also try to provide a way for the user to +fix the issue. This can be in the form of a simple log advice, as a diff advice +for a source code change, or as a command advice to prompt the user for a +direct action. In any case, don't forget to add the `FIXABLE` tag to the +diagnostic to highlight to the user it contains an actionable hint. +- Show don't tell: while log advices are highly versatile, always prefer +showing a rich advice like a code frame, diff or command to a textual +explanation if you can, since those are generally easier to understand and may +provide additional context.