222 lines
9.4 KiB
Markdown
222 lines
9.4 KiB
Markdown
|
|
# 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.
|