A Dart analyzer plugin for OverReact, bringing analysis-time checks and behavior to IDEs using the Dart Analysis Server (including JetBrains IDEs and VS Code).
Functionality includes checking for issues that cause React warnings/errors at runtime, linting for best practices, adding "assists" for common edits to component syntax and boilerplate, and more!
See the documentation links above for a full list of diagnostics/lints and assists.
- Add over_react to your pubspec.yaml
- Enable the plugin in your analysis_options.yaml:
analyzer: plugins: - over_react
- Restart the Dart Analysis Server in your IDE. The plugin may take a minute to load after built-in analysis completes.
Note that, currently, diagnostics only show up in your IDE, and not in command line tools like dart analyze
(see dart-lang/sdk#38407 for more info). As a result, running diagnostics in CI is not supported at this time.
The severity of specific diagnostics can optionally be adjusted in analysis_options.yaml. Individual diagnostics can also be disabled.
To configure the plugin, add a over_react
key to analysis_options.yaml:
analyzer:
plugins:
- over_react
over_react:
errors:
over_react_boilerplate_error: error
over_react_incorrect_doc_comment_location: warning
over_react_unnecessary_key: info
over_react_pseudo_static_lifecycle: ignore
In the example above, the first three diagnostics are adjusted to have severities of error
, warning
, and info
. The final diagnostic is disabled.
If a diagnostic is not listed in the analysis_options.yaml, it's default severity will be used.
See the analyzer_plugin package structure documentation for terminology and more info.
-
over_react: the "host" package
-
over_react_analyzer_plugin (
over_react/tools/analyzer_plugin
) - the "bootstrap" and "plugin" packages, mergedWe decided to merge these packages since it allows us to avoid creating a separate package for the plugin, which would have resulted in a more painful dev/release experience. (Since the plugin depends on over_react, we'd want to use monorepo to manage the packages. However, we can't do that currently, due to internal tooling restrictions that prevent us from having multiple packages declared in a single repository.)
-
playground - (
over_react/tools/analyzer_plugin/playground
) - a "target" package that consumes the plugin, useful for manually testing plugin during development
See analyzer_plugin's tutorial on building a plugin for general information on developing a Dart analyzer plugin.
Before you do anything: run
dart tool/init_local_dev.dart
Normally, the Dart Analysis Server does a one-time copy of plugin code into the Dart Analysis Server state folder (usually ~/.dartServer/.plugin_manager
), and never updates for plugins derived from path dependencies (TODO make dart-lang issue for this).
This means, normally, any changes you make to the plugin will not be reflected.
This script sets up a symlink to point to the original plugin directory (replacing any copy if it exists), so that changes are always reflected.
- Make changes to the plugin within the over_react_analyzer_plugin directory
- In the playground directory or in another package you've pulled the plugin into, restart the Analysis Server
- Wait for the Analysis Server to boot up, analyze, and run your updated plugin code
- Abide by diagnostic message best practices.
- Plugin code should be robust against invalid ASTs.
- As the user types, they produce invalid code, and the plugin shouldn't crash/break when this is the case.
- Defensively null-check on AST members.
- Code dealing with ASTs should make as few assumptions as possible.
- For example, code that used to assume a method declaration's parent was a class was likely broken with the introduction of extension methods.
- Use of
tryCast()
andancestorOfType()
makes this a lot easier.
- For hints/diagnostics, avoid producing false positives.
- Often, you may not have enough information to determine with full confidence whether code is problematic. The plugin should not emit diagnostics in this case to avoid creating excessive noise, causing the plugin to become less valuable or frustrating for users.
- Keep performance in mind.
- Diagnostics will run on potentially every Dart file in the project, and can severely affect user experience if they're slow. This is one of the bigger reasons why the Dart team avoided exposing analyzer plugin APIs for a while.
- Use prior art when possible instead of reinventing the wheel.
- The
analysis_server
package in the Dart SDK is where the majority of the built-in hints, errors, assists, quick fixes, etc. are implemented. We have the opportunity to reuse parts of their architecture, testing strategies, etc.
- The
- Avoid using
AstNode.toSource
andAstNode.childEntities
since they are approximations of the source.- If you need to get the source for a replacement, use
sourceFile.getText(node.offset, node.end)
.
- If you need to get the source for a replacement, use
Via AnalyzerDebugHelper
, it's possible for diagnostics to show debug information for specific locations in a file
as infos, which can be helpful to develop/troubleshoot
issues without having to attach a debugger.
Some parts of the plugin show this debug info based on the presence of a comment anywhere in the file. Currently-available debug info:
// debug: over_react_boilerplate
- shows how over_react boilerplate will be parsed/detected by the over_react builder and analyzer functionality dealing with component declarations// debug: over_react_metrics
- shows performance data on how long diagnostics took to run// debug: over_react_exhaustive_deps
- shows info on how dependencies were detected/interpreted// debug: over_react_required_props
- shows requiredness info for all props for each builder, as well as prop forwarding info
The dev experience when working on this plugin isn't ideal (See the analyzer_plugin
debugging docs for more information), but it's possible debug and see logs from the plugin.
These instructions are currently for JetBrains IDEs (IntelliJ, WebStorm, etc.) only.
Before starting, ensure you have the analyzer_plugin
open as its own project (rather than opening over_react
) in your IDE.
-
Ensure your Dart version is at least
2.8.3
. (The protocol connection was made available somewhere around this version) -
In your project, create a new Run Configuration using the
Dart Remote Debug
template -
Ensure the "Search sources in" section is pointing to the plugin package directory. Save your new Configuration. We'll come back to it later.
-
Open the "Registry" using the command palette (Command+Shift+A)
-
Find the
dart.server.vm.options
key and set the value to--observe=0
(allows access to the Observatory on a random, non-allocated port) -
Next, we need to open the analyzer diagnostics to find the URL for our debugger. Open the Dart Analysis Server Settings, and click
View analyzer diagnostics
. This will open your browser. -
In the Analysis Server Diagnostics page in your browser, click the
Memory and CPU Usage
tab. Copy the protocol connection URL. -
Run your newly created configuration by selecting it and clicking the "Debug" button
-
Finally, when prompted, paste the URL you just copied and click
OK
. -
In the debugger tab that was opened, verify that the debugger connected.
Congrats, you're debugging! 🎉
You can now set breakpoints, view logs, and do everything else you'd normally do in the debugger for.
All diagnostics and assists should be documented!!!
Documentation for the diagnostics and assists provided by this analyzer plugin are published to https://workiva.github.io/over_react/analyzer_plugin/.
This is accomplished by placing a @DocsMeta
annotation on a DiagnosticCode
for diagnostics, and an AssistKind
for assists. In order for them to work properly, the property the DiagnosticCode
or AssistKind
is assigned to must be a const
as shown in the examples below.
- Error Example: StyleMissingUnitDiagnostic
- Lint / Warning Example: ForwardOnlyDomPropsToDomBuildersDiagnostic
-
The value of
DiagnosticCode.name
should always start withover_react_
. -
The value of
DiagnosticCode.message
/DiagnosticCode.correction
/ should abide by the diagnostic message best practices used by official Dart analyzer diagnostics. Examples of official diagnostics messages.Additionally:
- The value of
DiagnosticCode.message
should not include the steps that users should take to correct the diagnostic.- Use
DiagnosticCode.correction
for this as shown in the examples above.
- Use
- The value of
-
The value of
DocsMeta.description
should use consistent terminology based on the severity of the diagnostic.AnalysisErrorSeverity.INFO
s andAnalysisErrorSeverity.WARNING
s should start with "Avoid" or "Prefer".AnalysisErrorSeverity.ERROR
s should start with "Do not", "Never" or "Always".
Check out the examples above for demonstrations of this.
-
The value of
FixKind.message
andAssistKind.message
should NOT end with a period. -
When possible, the value of
DocsMeta.description
(first argument passed to the@DocsMeta
annotation) should match the value ofDiagnosticCode.message
.- Sometimes this isn't possible as a result of using
errorMessageArgs
to pass dynamic information into the message.
- Sometimes this isn't possible as a result of using
-
The value of
DocsMeta.details
gets parsed as markdown, and should provide as much detail about the diagnostic as possible - including a "GOOD" and "BAD" example when possible (as shown in the examples above).
- When a new/updated diagnostic or assist merges to master, merge master into the
gh-pages
branch (it should merge cleanly). - From the
tools/analyzer_plugin/
directory, run:dart ./tool/doc.dart --gh-pages
- Commit the changes, and push them to the
gh-pages
branch. This will deploy the updated documentation to https://workiva.github.io/over_react/analyzer_plugin/ - typically within a matter of seconds.
We drew inspiration from the following:
- Flutter analysis functionality (actually built into the Dart SDK via analysis_server).
- React JS IDE plugins
- AngularDart analyzer plugin