blob: 6e70ca0cfa880c286c57a5f01ba118e9472e6271 [file] [view]
# 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.
After every edit to the `messages.yaml` file, you will need to run the utility
`analyzer/tool/messages/generate.dart` to update the generated files.
You also need to manually add the name of the code to the list of codes in two
files:
- `analyzer/lib/error/error.dart`
- `analysis_server/lib/src/services/correction/error_fix_status.yaml`
In the status file, the code should have the line
```yaml
status: needsEvaluation
```
nested under the name of the code.
## 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.