blob: a7bc2be978e78b4fef52f38b9939aa11a6096055 [file] [log] [blame] [view] [edit]
# Adding a new diagnostic
This document describes the process of adding a new (non-lint) diagnostic to the
analyzer.
## Define the diagnostic code
The first step is to define the code(s) associated with the diagnostic.
The codes are defined in the file `analyzer/messages.yaml`. There's a comment at
the top of the file describing the structure of the file.
Every diagnostic has at least one code associated with it. A code defines the
problem and correction messages that will be shown to users.
For most diagnostics, a single message (code) is sufficient. But sometimes it's
useful to tailor the message based on the context in which the problem occurs or
because the message can be made more clear.
For example, it's an error to declare two or more constructors with the same name.
That's true whether the name is explicit or implicit (the default constructor).
In order for the message to match the user's model of the language, we define
two messages for this one problem: one that refers to the constructors by their
name, and one that refers to the constructors as "unnamed". The way we define
two messages is by defining two codes.
Each code has a unique name (the key used in the map in `messages.yaml`) and can
optionally define a shared name that links all the codes for a single diagnostic
together. It is the shared name that is displayed to users. If a shared name
isn't explicitly provided, it will default to being the same as the unique name.
### Updating generated files
After every edit to the `messages.yaml` file, you will need to run the following
to update the generated files:
```bash
dart run analyzer/tool/messages/generate.dart
```
### Add code to `error_fix_status.yaml`
You also need to manually add the name of the code to the list of codes in the
file `analysis_server/lib/src/services/correction/error_fix_status.yaml`. The
code should have the line
```yaml
status: needsEvaluation
```
nested under the name of the code.
At some point, we'll evaluate the diagnostics that are marked as `needsEvaluation`.
If we can think of a reasonable and useful fix, then we'll change it to `needsFix`
with a comment indicating what fix we thought of. If we can't think of one, then
we'll change it to `noFix`. The status is changed to `hasFix` when there's at least
one fix associated with the diagnostic, which is something we can (and do) verify
in a test.
## Write tests
We recommend writing the tests for a diagnostic before writing the code to
generate the diagnostic. Doing so helps you think about the specific cases that
the implementation code needs to handle, which can result in cleaner
implementation code and fewer bugs.
The tests for each diagnostic code (or set of codes that have the same shared
name) are in a separate file in the directory `analyzer/test/src/diagnostics`.
Looking at the implementation of tests in a few of the other files can help you
see the basic pattern, but all the tests essentially work by setting up the code
to be analyzed, then assert that either the expected diagnostic has been
produced in the expected locations or that there are no diagnostics being
generated. (It's often valuable to test that the diagnostic doesn't have any
false positives.)
## Report the diagnostic
The last step is to write the code to report the diagnostic. Where that code
lives depends on the kind of diagnostic you're adding.
If you're adding a diagnostic that's defined by the language specification
(with a severity of 'error'), then the best place to implement it will usually
be in one of the `<node class>Resolver` classes.
If you're adding a warning, then the class `BestPracticesVerifier` is usually
the best place for it.
## Document the diagnostic
After the diagnostic has been implemented and committed, documentation for the
diagnostic needs to be written.
__Note:__ We are in the process of defining a process for this task, so this
section is currently incomplete. Contact us for details if you want to write the
documentation yourself. If you don't want to write the documentation then we'll
write it for you.
Before you start writing documentation we recommend that you look at several of
the existing examples of documentation to get a feeling for the general writing
style and format of the docs. Some of this information is also found below. If
you have questions about style, we follow the general
[Google style guidelines](https://developers.google.com/style/).
The documentation for the diagnostics that are implemented in the analyzer is in
the file `analyzer/messages.yaml`. They are under the key `documentation:`, and
are mostly standard markdown. The differences are described below.
### Template
The good news is that the documentation is highly stylized, so writing it is
usually fairly easy.
To start writing the documentation, copy the following template. Each section is
discussed below.
#### Description
The analyzer produces this diagnostic when
#### Example
The following code produces this diagnostic because :
```dart
```
#### Common fixes
```dart
```
### Description
The Description section should start by explaining _when_ the diagnostic will be
produced. Specifically, that means the conditions that cause the diagnostic to
be produced. The goal is to help the user understand why the diagnostic is
appearing in their code, so the explanation needs to cover all of the possible
reasons.
For example, the diagnostic `invalid_extension_argument_count` describes the
conditions that cause the diagnostic this way:
> The analyzer produces this diagnostic when an extension override doesn't
> have exactly one argument.
Unless it's fairly obvious, the description should also explain _why_ the
condition is being reported. In most cases the reason for the diagnostic will be
obvious from the description of when it's reported, but sometimes that isn't
enough.
For example, the user might not be familiar with extension overrides, so the
explanation above might not be sufficient. By explaining why an override must
have a single argument we can help the user learn about the feature, so the
documentation goes on to explain that:
> The argument is the expression used to compute the value of `this` within the
> extension method, so there must be one argument.
### Examples
The Examples section should show at least one example of code that will produce
the diagnostic. (If there's only one example, then the title should be singular
as in the template above, but if there are multiple examples the title should be
plural.)
The examples should be complete in the sense that the user should be able to
copy the example, paste it into an empty compilation unit, and see exactly the
one diagnostic being reported. (One technique we often use for this purpose is
to define local variables as parameters to a method or function so that the
`unused_local_variable` diagnostic isn't also reported.)
The examples should be minimal so that users aren't distracted by irrelevant
details. They should only include code that is required in order to generate
the diagnostic. They should use simple names, like `A`, `B`, and `C` for
classes, `M` for mixins, `f` and `g` for functions, and so on. It's better to
not use names with semantic value.
Each example must have the range of characters that are highlighted by the
diagnostic enclosed between `[!` and `!]` delimiters. These are used to
highlight the region on the web page and are also validated by tests.
Every code block must specify a file type, such as `dart` or `yaml`.
The examples occasionally need some additional support, which is provided by
"directives". The directives must be on the first lines of the code block, and
have the form `%directiveName=`. There are two directives defined.
The experiments directive (`%experiments=`) specifies a comma-separated list of
the names of language experiments that are to be enabled when analyzing the
example. This is necessary when writing documentation for experiments that
haven't yet shipped. These directives should be removed once the experiment is
enabled by default.
The uri directive (`%uri=`) specifies the URI for the file containing the code
block. Without a uri directive the standard uri will be used. This directive is
necessary for cases when an example needs an auxiliary file to be defined,
usually so that it can be imported in the example. Code blocks that have a uri
directive are considered to be auxiliary files, not examples, and aren't
analyzed or required to have highlight range markers. Auxiliary files exist
when either an example or a common fix is analyzed.
### Common fixes
The Common fixes section should show examples of the ways to fix the problem
that are most likely to be applicable. In some cases there's only one likely way
to fix the problem (like removing the null check operator when the expression
isn't nullable), but in other cases there might be multiple possible ways of
fixing the problem.
There should minimally be one fix shown for every action suggested by the
diagnostic's correction message.
Each fix should use one of the examples as the base and show how the invalid
code can be changed to apply the described fix. The same example can be used by
multiple fixes.
Each fix should be introduced by a sentence that explains what change the user
would make for the fix. Usually the description is written in the form "If
_these conditions hold_, then _make this change_:" so that users can tell when a
suggested fix can be applied.
Fixes can't have highlight markers, and are expected to not have any diagnostics
reported.