Skip to content

Latest commit

 

History

History

analyzer_plugin

OverReact Analyzer Plugin (beta)

OverReact Analyzer Plugin OverReact Analyzer Plugin

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.


How To Use It

  1. Add over_react to your pubspec.yaml
  2. Enable the plugin in your analysis_options.yaml:
    analyzer:
      plugins:
        - over_react
  3. 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.

Configuration

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.

Repo Structure

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, merged

    We 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

Local Development

See analyzer_plugin's tutorial on building a plugin for general information on developing a Dart analyzer plugin.

Setup

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.

Development cycle

  1. Make changes to the plugin within the over_react_analyzer_plugin directory
  2. In the playground directory or in another package you've pulled the plugin into, restart the Analysis Server
  3. Wait for the Analysis Server to boot up, analyze, and run your updated plugin code

Design Principles & Coding Strategies

  • 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() and ancestorOfType() 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.
  • Avoid using AstNode.toSource and AstNode.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).

Debugging the Plugin

Showing Debug Info

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

Attaching a Debugger

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.

  1. Ensure your Dart version is at least 2.8.3. (The protocol connection was made available somewhere around this version)

  2. In your project, create a new Run Configuration using the Dart Remote Debug template

    doc/create-configuration-1.png doc/create-configuration-2.png
  3. Ensure the "Search sources in" section is pointing to the plugin package directory. Save your new Configuration. We'll come back to it later.

    doc/create-configuration-3.png
  4. Open the "Registry" using the command palette (Command+Shift+A)

    doc/open-jetbrains-registry.png
  5. 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)

    doc/edit-jetbrains-registry.png
  6. 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.

    doc/open-analyzer-diagnostics.png
  7. In the Analysis Server Diagnostics page in your browser, click the Memory and CPU Usage tab. Copy the protocol connection URL.

    doc/find-the-port.png
  8. Run your newly created configuration by selecting it and clicking the "Debug" button

    doc/run-configuration-1.png
  9. Finally, when prompted, paste the URL you just copied and click OK.

    doc/run-configuration-2.png
  10. In the debugger tab that was opened, verify that the debugger connected.

    doc/verify-connected.png

Congrats, you're debugging! 🎉

You can now set breakpoints, view logs, and do everything else you'd normally do in the debugger for.

Documenting Diagnostics and Assists

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.

Examples

Best Practices

  • The value of DiagnosticCode.name should always start with over_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.
  • The value of DocsMeta.description should use consistent terminology based on the severity of the diagnostic.

    • AnalysisErrorSeverity.INFOs and AnalysisErrorSeverity.WARNINGs should start with "Avoid" or "Prefer".
    • AnalysisErrorSeverity.ERRORs should start with "Do not", "Never" or "Always".

    Check out the examples above for demonstrations of this.

  • The value of FixKind.message and AssistKind.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 of DiagnosticCode.message.

    • Sometimes this isn't possible as a result of using errorMessageArgs to pass dynamic information into the message.
  • 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).

Updating Published Documentation

  1. When a new/updated diagnostic or assist merges to master, merge master into the gh-pages branch (it should merge cleanly).
  2. From the tools/analyzer_plugin/ directory, run:
    dart ./tool/doc.dart --gh-pages
  3. 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.

Feature Ideas & Inspiration

We drew inspiration from the following: