Merge remote-tracking branch 'origin/main' into HEAD
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..97e6337
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+  - name: Dart team recommended lints
+    url: https://github.com/dart-lang/lints/issues/new/choose
+    about: Request a lint for inclusion, exclusion or otherwise discuss Dart team recommended lints.
diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/lint-bug-report.md
similarity index 82%
rename from .github/ISSUE_TEMPLATE/issue-report.md
rename to .github/ISSUE_TEMPLATE/lint-bug-report.md
index 4e3c5b5..ca6a62b 100644
--- a/.github/ISSUE_TEMPLATE/issue-report.md
+++ b/.github/ISSUE_TEMPLATE/lint-bug-report.md
@@ -1,6 +1,6 @@
 ---
-name: Issue report
-about: Create a report to help us make the linter better.
+name: Lint bug report
+about: Report a bug with, or propose a modification to, an existing lint.
 title: ''
 labels: ''
 assignees: ''
diff --git a/.github/ISSUE_TEMPLATE/lint-proposal.md b/.github/ISSUE_TEMPLATE/lint-proposal.md
new file mode 100644
index 0000000..35dfb70
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/lint-proposal.md
@@ -0,0 +1,50 @@
+---
+name: Lint proposal
+about: Propose a new lint rule or a modification to an existing one.
+title: 'proposal: `<rule_name>`'
+labels: 'lint proposal, status: pending'
+assignees: ''
+
+---
+
+## <lint_name>
+
+*Replace <lint_name> above with your proposed rule name.  (See notes on naming in [Writing Lints].)*
+
+## Description
+
+*Provide a short description suitable for display in console output.*
+
+## Details
+
+*Give a detailed description that should provide context and motivation.  Ideally this could be used directly in the rule's documentation.*
+
+## Kind
+
+*Does this enforce style advice?  Guard against errors?  Other?*
+
+## Bad Examples
+
+*Add a few examples that demonstrate where this lint should fire.*
+
+## Good Examples 
+
+*Add a few examples that demonstrate a “good” adoption of this lint’s principle.*
+
+## Discussion
+
+*Add any other motivation or useful context here.*
+
+### Discussion checklist
+
+- [ ] List any existing rules this proposal modifies, complements, overlaps or conflicts with.
+- [ ] List any relevant issues (reported here, the [SDK Tracker], or elsewhere).
+- [ ] If there's any prior art (e.g., in other linters), please add references here.
+- [ ] If this proposal corresponds to [Effective Dart] or [Flutter Style Guide] advice, please call it out. (If there isn't any corresponding advice, should there be?)
+- [ ] If this proposal is motivated by real-world examples, please provide as many details as you can.  Demonstrating potential impact is _especially valuable_.
+
+<!-- Links -->
+[Writing Lints]: https://github.com/dart-lang/linter/blob/main/doc/writing-lints.md
+[Effective Dart]: https://dart.dev/guides/language/effective-dart
+[Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
+[SDK Tracker]: https://github.com/dart-lang/sdk/issues
diff --git a/.github/ISSUE_TEMPLATE/lint-quick-fix-request.md b/.github/ISSUE_TEMPLATE/lint-quick-fix-request.md
new file mode 100644
index 0000000..0b4e97b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/lint-quick-fix-request.md
@@ -0,0 +1,17 @@
+---
+name: Request a quick-fix for a lint
+about: Request that a quick-fix be added for an existing lint.
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Describe the quick-fix**
+Include as much detail as you can. 
+
+**Examples**
+Ideally, provide examples of the quick-fix applied.
+
+**Additional context**
+Add any other considerations or context here.
diff --git a/.github/ISSUE_TEMPLATE/lint-request.md b/.github/ISSUE_TEMPLATE/lint-request.md
deleted file mode 100644
index e513e66..0000000
--- a/.github/ISSUE_TEMPLATE/lint-request.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-name: Lint request
-about: Request a new lint rule.
-title: ''
-labels: enhancement, lint request
-assignees: ''
-
----
-
-**Describe the rule you'd like to see implemented**
-Include as much detail as you can. 
-
-This should include a description of who would use the lint (such as if it's
-specific to a framework like Flutter), what conditions the rule should look
-for, and what conditions the rule should ignore.
-
-**Examples**
-
-Ideally, provide motivating examples w/ rationale.
-
-**Additional context**
-Add any other considerations or context here.
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..2144819
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,8 @@
+# Dependabot configuration file.
+version: 2
+
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 40cab31..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-# Set update schedule for GitHub Actions
-# See https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot
-
-version: 2
-updates:
-
-- package-ecosystem: "github-actions"
-  directory: "/"
-  schedule:
-    # Check for updates to GitHub Actions every weekday
-    interval: "monthly"
diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml
new file mode 100644
index 0000000..3dff139
--- /dev/null
+++ b/.github/issue-labeler.yml
@@ -0,0 +1,10 @@
+# Auto-generated by `tool/labeler/issue_config.dart`
+
+set-core:
+  - '(avoid_empty_else|avoid_relative_lib_imports|avoid_shadowing_type_parameters|avoid_types_as_parameter_names|await_only_futures|camel_case_extensions|camel_case_types|curly_braces_in_flow_control_structures|depend_on_referenced_packages|empty_catches|file_names|hash_and_equals|iterable_contains_unrelated_type|list_remove_unrelated_type|no_duplicate_case_values|non_constant_identifier_names|null_check_on_nullable_type_parameter|package_prefixed_library_names|prefer_generic_function_type_aliases|prefer_is_empty|prefer_is_not_empty|prefer_iterable_whereType|prefer_typing_uninitialized_variables|provide_deprecation_message|unnecessary_overrides|unrelated_type_equality_checks|valid_regexps|void_checks)'
+
+set-recommended:
+  - '(always_require_non_null_named_parameters|annotate_overrides|avoid_function_literals_in_foreach_calls|avoid_init_to_null|avoid_null_checks_in_equality_operators|avoid_renaming_method_parameters|avoid_return_types_on_setters|avoid_returning_null_for_void|avoid_single_cascade_in_expression_statements|constant_identifier_names|control_flow_in_finally|empty_constructor_bodies|empty_statements|exhaustive_cases|implementation_imports|library_names|library_prefixes|library_private_types_in_public_api|no_leading_underscores_for_library_prefixes|no_leading_underscores_for_local_identifiers|null_closures|overridden_fields|package_names|prefer_adjacent_string_concatenation|prefer_collection_literals|prefer_conditional_assignment|prefer_contains|prefer_equal_for_default_values|prefer_final_fields|prefer_for_elements_to_map_fromIterable|prefer_function_declarations_over_variables|prefer_if_null_operators|prefer_initializing_formals|prefer_inlined_adds|prefer_interpolation_to_compose_strings|prefer_is_not_operator|prefer_null_aware_operators|prefer_spread_collections|prefer_void_to_null|recursive_getters|slash_for_doc_comments|type_init_formals|unnecessary_brace_in_string_interps|unnecessary_const|unnecessary_constructor_name|unnecessary_getters_setters|unnecessary_late|unnecessary_new|unnecessary_null_aware_assignments|unnecessary_null_in_if_null_operators|unnecessary_nullable_for_final_variable_declarations|unnecessary_string_escapes|unnecessary_string_interpolations|unnecessary_this|use_function_type_syntax_for_parameters|use_rethrow_when_possible)'
+
+set-flutter:
+  - '(avoid_print|avoid_unnecessary_containers|avoid_web_libraries_in_flutter|no_logic_in_create_state|prefer_const_constructors|prefer_const_constructors_in_immutables|prefer_const_declarations|prefer_const_literals_to_create_immutables|sized_box_for_whitespace|sort_child_properties_last|use_build_context_synchronously|use_full_hex_values_for_flutter_colors|use_key_in_widget_constructors)'
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000..d8b2e19
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,105 @@
+# Auto-generated by `tool/labeler/pr_config.dart`
+
+set-core:
+- lib/**/avoid_empty_else.dart
+- lib/**/avoid_relative_lib_imports.dart
+- lib/**/avoid_shadowing_type_parameters.dart
+- lib/**/avoid_types_as_parameter_names.dart
+- lib/**/await_only_futures.dart
+- lib/**/camel_case_extensions.dart
+- lib/**/camel_case_types.dart
+- lib/**/curly_braces_in_flow_control_structures.dart
+- lib/**/depend_on_referenced_packages.dart
+- lib/**/empty_catches.dart
+- lib/**/file_names.dart
+- lib/**/hash_and_equals.dart
+- lib/**/iterable_contains_unrelated_type.dart
+- lib/**/list_remove_unrelated_type.dart
+- lib/**/no_duplicate_case_values.dart
+- lib/**/non_constant_identifier_names.dart
+- lib/**/null_check_on_nullable_type_parameter.dart
+- lib/**/package_prefixed_library_names.dart
+- lib/**/prefer_generic_function_type_aliases.dart
+- lib/**/prefer_is_empty.dart
+- lib/**/prefer_is_not_empty.dart
+- lib/**/prefer_iterable_whereType.dart
+- lib/**/prefer_typing_uninitialized_variables.dart
+- lib/**/provide_deprecation_message.dart
+- lib/**/unnecessary_overrides.dart
+- lib/**/unrelated_type_equality_checks.dart
+- lib/**/valid_regexps.dart
+- lib/**/void_checks.dart
+
+set-recommended:
+- lib/**/always_require_non_null_named_parameters.dart
+- lib/**/annotate_overrides.dart
+- lib/**/avoid_function_literals_in_foreach_calls.dart
+- lib/**/avoid_init_to_null.dart
+- lib/**/avoid_null_checks_in_equality_operators.dart
+- lib/**/avoid_renaming_method_parameters.dart
+- lib/**/avoid_return_types_on_setters.dart
+- lib/**/avoid_returning_null_for_void.dart
+- lib/**/avoid_single_cascade_in_expression_statements.dart
+- lib/**/constant_identifier_names.dart
+- lib/**/control_flow_in_finally.dart
+- lib/**/empty_constructor_bodies.dart
+- lib/**/empty_statements.dart
+- lib/**/exhaustive_cases.dart
+- lib/**/implementation_imports.dart
+- lib/**/library_names.dart
+- lib/**/library_prefixes.dart
+- lib/**/library_private_types_in_public_api.dart
+- lib/**/no_leading_underscores_for_library_prefixes.dart
+- lib/**/no_leading_underscores_for_local_identifiers.dart
+- lib/**/null_closures.dart
+- lib/**/overridden_fields.dart
+- lib/**/package_names.dart
+- lib/**/prefer_adjacent_string_concatenation.dart
+- lib/**/prefer_collection_literals.dart
+- lib/**/prefer_conditional_assignment.dart
+- lib/**/prefer_contains.dart
+- lib/**/prefer_equal_for_default_values.dart
+- lib/**/prefer_final_fields.dart
+- lib/**/prefer_for_elements_to_map_fromIterable.dart
+- lib/**/prefer_function_declarations_over_variables.dart
+- lib/**/prefer_if_null_operators.dart
+- lib/**/prefer_initializing_formals.dart
+- lib/**/prefer_inlined_adds.dart
+- lib/**/prefer_interpolation_to_compose_strings.dart
+- lib/**/prefer_is_not_operator.dart
+- lib/**/prefer_null_aware_operators.dart
+- lib/**/prefer_spread_collections.dart
+- lib/**/prefer_void_to_null.dart
+- lib/**/recursive_getters.dart
+- lib/**/slash_for_doc_comments.dart
+- lib/**/type_init_formals.dart
+- lib/**/unnecessary_brace_in_string_interps.dart
+- lib/**/unnecessary_const.dart
+- lib/**/unnecessary_constructor_name.dart
+- lib/**/unnecessary_getters_setters.dart
+- lib/**/unnecessary_late.dart
+- lib/**/unnecessary_new.dart
+- lib/**/unnecessary_null_aware_assignments.dart
+- lib/**/unnecessary_null_in_if_null_operators.dart
+- lib/**/unnecessary_nullable_for_final_variable_declarations.dart
+- lib/**/unnecessary_string_escapes.dart
+- lib/**/unnecessary_string_interpolations.dart
+- lib/**/unnecessary_this.dart
+- lib/**/use_function_type_syntax_for_parameters.dart
+- lib/**/use_rethrow_when_possible.dart
+
+set-flutter:
+- lib/**/avoid_print.dart
+- lib/**/avoid_unnecessary_containers.dart
+- lib/**/avoid_web_libraries_in_flutter.dart
+- lib/**/no_logic_in_create_state.dart
+- lib/**/prefer_const_constructors.dart
+- lib/**/prefer_const_constructors_in_immutables.dart
+- lib/**/prefer_const_declarations.dart
+- lib/**/prefer_const_literals_to_create_immutables.dart
+- lib/**/sized_box_for_whitespace.dart
+- lib/**/sort_child_properties_last.dart
+- lib/**/use_build_context_synchronously.dart
+- lib/**/use_full_hex_values_for_flutter_colors.dart
+- lib/**/use_key_in_widget_constructors.dart
+
diff --git a/.github/move.yml b/.github/move.yml
deleted file mode 100644
index 441664a..0000000
--- a/.github/move.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-# Configuration for move-issues - https://github.com/dessant/move-issues
-# See also: https://github.com/dart-lang/sdk/blob/master/.github/move.yml
-
-# Delete the command comment when it contains no other content
-deleteCommand: true
-
-# Close the source issue after moving
-closeSourceIssue: true
-
-# Lock the source issue after moving
-lockSourceIssue: false
-
-# Mention issue and comment authors
-mentionAuthors: true
-
-# Preserve mentions in the issue content
-keepContentMentions: true
-
-# Set custom aliases for targets
-aliases:
-  flutter: flutter/flutter
-  flutter-intellij: flutter/flutter-intellij
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 449a4db..31ff2cb 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -7,13 +7,17 @@
   pull_request:
   push:
     branches:
-      - master
+      - main
 
 env:
   PUB_ENVIRONMENT: bot.github
 
+permissions: read-all
+
 jobs:
   build:
+    permissions:
+      checks: write  # for coverallsapp/github-action to create new checks
     name: ${{ matrix.linter-bot }}
     runs-on: ubuntu-latest
     strategy:
@@ -23,13 +27,12 @@
           - main
           - coverage
           - release
-          # re-enable when pana CI issues are resolved: https://github.com/dart-lang/linter/issues/2504
-          # - pana_baseline
+          - pana_baseline
           - benchmark
 
     steps:
-      - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v1
+      - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
+      - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d
         with:
           sdk: dev
 
@@ -41,7 +44,7 @@
         run: ./tool/bot.sh
       - name: upload coverage
         if: matrix.linter-bot == 'coverage'
-        uses: coverallsapp/github-action@v1.1.2
+        uses: coverallsapp/github-action@9ba913c152ae4be1327bfb9085dc806cedb44057
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           path-to-lcov: var/lcov.info
diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
new file mode 100644
index 0000000..81b6aba
--- /dev/null
+++ b/.github/workflows/gh-pages.yml
@@ -0,0 +1,36 @@
+name: Deploy to GitHub Pages
+
+on:
+  push:
+    branches:
+      - main
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+  build-and-deploy-docs:
+    permissions:
+      contents: write
+    runs-on: ubuntu-latest
+    if: github.repository == 'dart-lang/linter'
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
+      - name: Setup Dart
+        uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d
+        with:
+          sdk: stable
+      - name: Get dependencies
+        run: dart pub get
+      - name: Generate docs
+        run: dart run tool/doc.dart --create-dirs --no-markdown --out lints --token ${{ secrets.GITHUB_TOKEN }}
+      - name: Deploy docs
+        uses: peaceiris/actions-gh-pages@de7ea6f8efb354206b205ef54722213d99067935
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_branch: gh-pages
+          publish_dir: lints
+          destination_dir: lints
+          keep_files: true
diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml
new file mode 100644
index 0000000..02ee762
--- /dev/null
+++ b/.github/workflows/issue-labeler.yml
@@ -0,0 +1,21 @@
+name: "issue labeler"
+
+on:
+  issues:
+    types: [opened, edited]
+
+permissions: read-all
+
+jobs:
+  triage:
+    permissions:
+      contents: read
+      issues: write
+    runs-on: ubuntu-latest
+    steps:
+      - uses: github/issue-labeler@6ca237433dbbb8e475241b7f38f4600d9e296c57
+        with:
+          repo-token: "${{ secrets.GITHUB_TOKEN }}"
+          configuration-path: .github/issue-labeler.yml
+          enable-versioned-regex: 0
+          include-title: 1
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 0000000..a3b77ef
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,17 @@
+name: "labeler"
+
+on:
+  - pull_request_target
+
+permissions: read-all
+
+jobs:
+  triage:
+    permissions:
+      contents: read
+      pull-requests: write
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/labeler@5c7539237e04b714afd8ad9b4aed733815b9fab4
+        with:
+          repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml
new file mode 100644
index 0000000..4d61f36
--- /dev/null
+++ b/.github/workflows/linkcheck.yml
@@ -0,0 +1,16 @@
+name: link check
+
+on:
+  - pull_request_target
+
+permissions: read-all
+
+jobs:
+  check-links:
+    name: link check
+    runs-on: ubuntu-latest
+    steps:
+      - name: link check
+        uses: filiph/linkcheck@2.0.23
+        with:
+          arguments: -e https://dart-lang.github.io/linter/lints/index.html
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
new file mode 100644
index 0000000..c2248d2
--- /dev/null
+++ b/.github/workflows/no-response.yml
@@ -0,0 +1,33 @@
+# A workflow to close issues where the author hasn't responded to a request for
+# more information; see https://github.com/godofredoc/no-response for docs.
+
+name: response check
+
+# Both `issue_comment` and `scheduled` event types are required.
+on:
+  issue_comment:
+    types: [created]
+  schedule:
+    # Schedule for five minutes after the hour, every hour
+    - cron: '5 * * * *'
+
+# All permissions not specified are set to 'none'.
+permissions:
+  issues: write
+
+jobs:
+  noResponse:
+    runs-on: ubuntu-latest
+    if: ${{ github.repository_owner == 'dart-lang' }}
+    steps:
+      - uses: godofredoc/no-response@0ce2dc0e63e1c7d2b87752ceed091f6d32c9df09
+        with:
+          responseRequiredLabel: "needs-info"
+          daysUntilClose: 14
+          # Comment to post when closing an Issue for lack of response.
+          closeComment: >
+            Without additional information we're not able to resolve this
+            issue, so it will be closed at this time. You're still free to add
+            more info and respond to any questions above, though. We'll reopen
+            the case if you do. Thanks for your contribution!
+          token: ${{ github.token }}
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
new file mode 100644
index 0000000..38e040b
--- /dev/null
+++ b/.github/workflows/scorecards-analysis.yml
@@ -0,0 +1,54 @@
+name: Scorecards supply-chain security
+on:
+  # Only the default branch is supported.
+  branch_protection_rule:
+  push:
+    branches: [ main ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+  analysis:
+    name: Scorecards analysis
+    runs-on: ubuntu-latest
+    permissions:
+      # Needed to upload the results to code-scanning dashboard.
+      security-events: write
+      id-token: write
+      actions: read
+      contents: read
+
+    steps:
+      - name: "Checkout code"
+        uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
+        with:
+          persist-credentials: false
+
+      - name: "Run analysis"
+        uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d
+        with:
+          results_file: results.sarif
+          results_format: sarif
+          # Read-only PAT token. To create it,
+          # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
+          repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
+          # Publish the results to enable scorecard badges. For more details, see
+          # https://github.com/ossf/scorecard-action#publishing-results.
+          # For private repositories, `publish_results` will automatically be set to `false`,
+          # regardless of the value entered here.
+          publish_results: true
+
+      # Upload the results as artifacts (optional).
+      - name: "Upload artifact"
+        uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb
+        with:
+          name: SARIF file
+          path: results.sarif
+          retention-days: 5
+
+      # Upload the results to GitHub's code scanning dashboard.
+      - name: "Upload to code-scanning"
+        uses: github/codeql-action/upload-sarif@ec3cf9c605b848da5f1e41e8452719eb1ccfb9a6
+        with:
+          sarif_file: results.sarif
diff --git a/AUTHORS b/AUTHORS
index 12d0d42..ca99c72 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -15,6 +15,7 @@
 Patrice Chalin <pchalin@gmail.com>
 Maxim Dikun <dikmax@gmail.com>
 Bernardo Ferrari <bernaferrari2@gmail.com>
+David Fong <davidfong19@gmail.com>
 Xavier Hainaux <xavier.hainaux@gmail.com>
 Alexey Knyazev <lexa.knyazev@gmail.com>
 Jawahar <jawahars_16@live.in>
@@ -22,12 +23,25 @@
 Benjamin Li <benjamin.voila@gmail.com>
 Guillermo López-Anglada <guillermo.lopez@outlook.com>
 Parker Lougheed <parlough@gmail.com>
+David Martos <davidmartos96@gmail.com>
 Juan Pablo Paulsen <juanpablo.paulsen@gmail.com>
+Mathieu Payette <matpayette@hotmail.com>
 Luke Pighetti <git@lukepighetti.com>
 Sem van Rij <sem@lekkerplakjekaas.nl>
+Michael Joseph Rosenthal <rosenthalm93@gmail.com>
 Muhammed Salih Guler <muhammedsalihguler@gmail.com>
+Lucas Saudon <lsaudon@gmail.com>
 Nolan Scobie <nolan.scobie@gmail.com>
+TAKAHASHI Shuuji <shuuji3@gmail.com>
 Cameron Steffen <cam.steffen94@gmail.com>
 Danny Tuppeny <danny@tuppeny.com>
 Ivan Tugay <listepo@gmail.com>
 Rohan Vanheusden <rohan.vanheusden@gmail.com>
+Minh Dao <asiancorporator@yahoo.de>
+Sai Sandeep Mutyala <rageofepicfury@gmail.com>
+Kristen McWilliam <kmcwilliampublic@gmail.com>
+Paul Köhler <kontakt@paul-koehler.dev>
+Felipe Morschel <fmorschel.contact@gmail.com>
+Nils Reichardt <hi@nils.re>
+Alex Li <alexv.525.li@gmail.com>
+Michail Smirnov <miva200267@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 157f81e..e60ecc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,278 @@
+# 1.31.0
+
+- updated `prefer_equal_for_default_values` to not report for SDKs `>=2.19`,
+  where this lint is now an analyzer diagnostic.
+- updated `unrelated_type_equality_checks` to support updated `package:fixnum`
+  structure.
+
+# 1.30.0
+
+- new lint: `enable_null_safety`
+- new lint: `library_annotations`
+- miscellaneous documentation improvements
+
+# 1.29.0
+
+- new lint: `dangling_library_doc_comments`
+- fix `no_leading_underscores_for_local_identifiers` to not report super formals as local variables
+- fix `unnecessary_overrides` false negatives
+- fix `cancel_subscriptions` for nullable fields 
+- new lint: `collection_methods_unrelated_type`
+- update `library_names` to support unnamed libraries
+- fix `unnecessary_parenthesis` support for as-expressions
+- fix `use_build_context_synchronously` to check for context property accesses
+- fix false positive in `comment_references`
+- improved unrelated type checks to handle enums and cascades
+- fix `unnecessary_brace_in_string_interps` for `this` expressions 
+- update `use_build_context_synchronously` for `BuildContext.mounted`
+- improve `flutter_style_todos` to handle more cases
+- fix `use_build_context_synchronously` to check for `BuildContext`s in named expressions
+- fix `exhaustive_cases` to check parenthesized expressions
+- performance improvements for:
+  - `avoid_null_checks_in_equality_operators`
+  - `join_return_with_statement`
+  - `recursive_getters`
+  - `unnecessary_lambdas`
+  - `diagnostic_describe_all_properties`
+  - `prefer_foreach`
+  - `avoid_escaping_inner_quotes`
+  - `cascade_invocations`
+  - `tighten_type_of_initializing_formals`
+  - `prefer_interpolation_to_compose_strings`
+  - `prefer_constructors_over_static_methods`
+  - `avoid_returning_null`
+  - `parameter_assignments`
+  - `prefer_constructors_over_static_methods`
+  - `prefer_interpolation_to_compose_strings`
+  - `avoid_returning_null`
+  - `avoid_returning_this`
+  - `flutter_style_todos`
+  - `avoid_positional_boolean_parameters`
+  - `prefer_const_constructors`
+- new lint: `implicit_call_tearoffs`
+- new lint: `unnecessary_library_directive`
+
+# 1.28.0
+
+- update `avoid_redundant_argument_values` to work with enum declarations
+- performance improvements for `prefer_contains`
+- new lint: `unreachable_from_main`
+- (internal): analyzer API updates and `DartTypeUtilities` refactoring
+
+# 1.27.0
+
+- fix `avoid_redundant_argument_values` when referencing required 
+  parameters in legacy libraries
+- performance improvements for `use_late_for_private_fields_and_variables`
+- new lint: `use_string_in_part_of_directives`
+- fixed `use_super_parameters` false positive with repeated super
+  parameter references
+- updated `use_late_for_private_fields_and_variables` to handle enums
+- fixed `prefer_contains` false positive when start index is non-zero
+- improved `noop_primitive_operations` to catch `.toString()`
+  in string interpolations
+- updated `public_member_api_docs` to report diagnostics on extension
+  names (instead of bodies)
+- miscellaneous documentation improvements
+- (internal): `DartTypeUtilities` refactoring
+
+# 1.26.0
+
+- new lint: `combinators_ordering`
+- fixed `use_colored_box` and `use_decorated_box` to not over-report
+  on containers without a child
+- fixed false positive for `unnecessary_parenthesis` on a map-or-set
+  literal at the start of an expression statement
+- fixed false positive for `prefer_final_locals` reporting on fields
+- fixed `unnecessary_overrides` to allow overrides on `@Protected`
+  members
+- fixed `avoid_multiple_declarations_per_line` false positive in
+  `for` statements
+- fixed `prefer_final_locals` false positive on declaration lists
+  with at least one non-final variable
+- fixed `use_build_context_synchronously` to handle `await`s in
+  `if` conditions
+
+# 1.25.0
+
+- new lint: `discarded_futures`
+- improved message and highlight range for
+  `no_duplicate_case_values`
+- performance improvements for `lines_longer_than_80_chars`,
+  `prefer_const_constructors_in_immutables`, and
+  `prefer_initializing_formals`
+
+# 1.24.0
+
+- fix `prefer_final_parameters` to support super parameters
+- new lint: `unnecessary_to_list_in_spreads`
+- fix `unawaited_futures` to handle string interpolated
+  futures
+- update `use_colored_box` to not flag nullable colors
+- new lint: `unnecessary_null_aware_operator_on_extension_on_nullable`
+
+# 1.23.0
+
+- fixed `no_leading_underscores_for_local_identifiers`
+  to lint local function declarations 
+- fixed `avoid_init_to_null` to correctly handle super
+  initializing defaults that are non-null
+- updated `no_leading_underscores_for_local_identifiers`
+  to allow identifiers with just underscores
+- fixed `flutter_style_todos` to support usernames that
+  start with a digit
+- updated `require_trailing_commas` to handle functions
+  in asserts and multi-line strings
+- updated `unsafe_html` to allow assignments to 
+  `img.src`
+- fixed `unnecessary_null_checks` to properly handle map
+  literal entries
+
+# 1.22.0
+
+- fixed false positives for `unnecessary_getters_setters`
+  and `prefer_final_fields`with enhanced enums
+- updated to analyzer 3.4.0 APIs
+- fixed null-safe variance in `invariant_booleans`
+
+# 1.21.2
+
+- several `use_super_parameters` false positive fixes
+- updated `depend_on_referenced_packages` to treat
+  `flutter_gen` as a virtual package, not needing an
+  explicit dependency
+
+# 1.21.1
+
+- bumped language lower-bound constraint to `2.15.0`
+
+# 1.21.0
+
+- fixed `use_key_in_widget_constructors` false positive
+  with `key` super parameter initializers
+- fixed `use_super_parameters` false positive with field
+  formal params
+- updated `unnecessary_null_checks` and 
+  `null_check_on_nullable_type_parameter` to handle
+  list/set/map literals, and `yield` and `await`
+  expressions
+
+# 1.20.0
+
+- renamed `use_super_initializers` to `use_super_parameters`
+- fixed `unnecessary_null_aware_assignments` property-access
+  false positive
+
+# 1.19.2
+
+- new lint: `use_super_initializers`
+- new lint: `use_enums`
+- new lint: `use_colored_box`
+- performance improvements for `sort_constructors`
+- doc improvements for `always_use_package_imports`,
+  `avoid_print`, and `avoid_relative_lib_imports` 
+- update `avoid_void_async` to skip `main` functions
+- update `prefer_final_parameters` to not super on super params
+- lint updates for enhanced-enums and super-initializer language
+  features
+- updated `unnecessary_late` to report on the variable name
+- marked `null_check_on_nullable_type_parameter` stable
+
+# 1.18.0
+
+- extend `camel_case_types` to cover enums
+- fix `no_leading_underscores_for_local_identifiers` to not 
+  mis-flag field formal parameters with default values
+- fix `prefer_function_declarations_over_variables` to not
+  mis-flag non-final fields
+- performance improvements for `prefer_contains`
+- update `exhaustive_cases` to skip deprecated values that
+  redirect to other values
+
+# 1.17.1
+
+- update to `analyzer` version 3.0
+
+# 1.17.0
+
+- new lint: `unnecessary_late`
+- fix to `no_leading_underscores_for_local_identifiers` to allow
+  underscores in catch clauses
+
+# 1.16.0
+
+- doc improvements for `prefer_initializing_formals`
+- updates to `secure_pubspec_urls` to check `issue_tracker` and
+  `repository` entries
+- new lint: `conditional_uri_does_not_exist`
+- performance improvements for 
+  `missing_whitespace_between_adjacent_strings`
+
+# 1.15.0
+
+- new lint: `use_decorated_box`
+- new lint: `no_leading_underscores_for_library_prefixes`
+- new lint: `no_leading_underscores_for_local_identifiers`
+- new lint: `secure_pubspec_urls`
+- new lint: `sized_box_shrink_expand`
+- new lint: `avoid_final_parameters`
+- improved docs for `omit_local_variable_types`
+
+# 1.14.0
+
+- fix `omit_local_variable_types` to not flag a local type that is 
+  required for inference
+
+# 1.13.0
+
+- allow `while (true) { ...}` in `literal_only_boolean_expressions`
+- fixed `file_names` to report at the start of the file (not the entire 
+  compilation unit)
+- fixed `prefer_collection_literals` named typed param false positive
+- control flow improvements for `use_build_context_synchronously`
+
+# 1.12.0
+
+- fixed `unnecessary_lambdas` false positive for const constructor invocations
+- updated `avoid_print` to allow `kDebugMode`-wrapped print calls
+- fixed handling of initializing formals in `prefer_final_parameters`
+- fixed `unnecessary_parenthesis` false positive with function expressions
+
+# 1.11.0
+
+- added support for constructor tear-offs to `avoid_redundant_argument_values`, 
+  `unnecessary_lambdas`, and `unnecessary_parenthesis`
+- new lint: `unnecessary_constructor_name` to flag unnecessary uses of `.new`
+
+# 1.10.0
+
+- improved regular expression parsing performance for common checks 
+  (`camel_case_types`, `file_names`, etc.)
+- (internal) migrated to analyzer 2.1.0 APIs
+- fixed false positive in `use_build_context_synchronously` in awaits inside 
+  anonymous functions
+- fixed `overridden_fields` false positive w/ static fields
+- fixed false positive in `avoid_null_checks_in_equality_operators` w/ 
+  non-nullable params
+- fixed false positive for deferred imports in `prefer_const_constructors`
+
+# 1.9.0
+
+- marked `avoid_dynamic_calls` stable
+- (internal) removed unused `MockPubVisitor` and `MockRule` classes
+- fixed `prefer_void_to_null` false positive w/ overridden properties
+- (internal) removed references to `NodeLintRule` in lint rule declarations
+- fixed `prefer_void_to_null` false positive on overriding returns
+- fixed `prefer_generic_function_type_aliases` false positive w/ incomplete statements
+- fixed false positive for `prefer_initializing_formals` with factory constructors
+- fixed `void_checks` false positives with incomplete source
+- updated `unnecessary_getters_setters` to only flag the getter
+- improved messages for `avoid_renaming_method_parameters`
+- fixed false positive in `prefer_void_to_null`
+- fixed false positive in `omit_local_variable_types`
+- fixed false positive in `use_rethrow_when_possible`
+- performance improvements for `annotate_overrides`, `prefer_contains`, and `prefer_void_to_null`
+
 # 1.8.0
 
 - performance improvements for `prefer_is_not_empty`
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 91fbad9..bd98b80 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -41,15 +41,15 @@
 Contributing code is easy.
 
  * First, get the source by forking `https://github.com/dart-lang/linter` into your own GitHub account.
- * If you haven't configured your machine with an SSH key that's known to github then follow the directions here: https://help.github.com/articles/generating-ssh-keys/.
+ * If you haven't configured your machine with an SSH key that's known to GitHub then follow the directions here: https://help.github.com/articles/generating-ssh-keys/.
  * `git clone git@github.com:<your_name_here>/linter.git`
  * `cd linter`
- * `git remote add upstream git@github.com:dart-lang/linter.git` (So that you fetch from the master repository, not your clone, when running git fetch et al.)
+ * `git remote add upstream git@github.com:dart-lang/linter.git` (So that you fetch from the main repository, not your clone, when running git fetch et al.)
 
 To start working on a patch:
 
  * `git fetch upstream`
- * `git checkout upstream/master -b name_of_your_branch`
+ * `git checkout upstream/main -b name_of_your_branch`
  * Hack away.  (Before committing, please be sure and run `dart format` on modified files; our build will fail if you don't!)
  * `git commit -a -m "<your informative commit message>"`
  * `git push origin name_of_your_branch`
@@ -59,17 +59,17 @@
  * `git pull-request` (if you are using [Hub](https://github.com/github/hub/)) or
   go to `https://github.com/dart-lang/linter` and click the
   "Compare & pull request" button
- * either explicitly name a reviewer in the github UI or add their github name in the pull request message body
+ * either explicitly name a reviewer in the GitHub UI or add their GitHub name in the pull request message body
 
 Please make sure all your checkins have detailed commit messages explaining the patch and if a PR is *not* ready to land, consider making it clear in the description and/or prefixing the title with "WIP".
 
 Once you've gotten an LGTM from a project maintainer, submit your changes to the
-`master` branch using one of the following methods:
+`main` branch using one of the following methods:
 
 * Wait for one of the project maintainers to submit it for you.
 * Click the green "Merge pull request" button on the GitHub UI of your pull
   request (requires commit access)
-* `git push upstream name_of_your_branch:master` (requires commit access)
+* `git push upstream name_of_your_branch:main` (requires commit access)
 * Having done all this, please make sure we have a good email address so we can credit you in our `AUTHORS` file.
 * Thank you!
 
diff --git a/README.md b/README.md
index 24f7cdb..4b5a368 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@
 [![Build Status](https://github.com/dart-lang/linter/workflows/linter/badge.svg)](https://github.com/dart-lang/linter/actions)

 [![Coverage Status](https://coveralls.io/repos/dart-lang/linter/badge.svg)](https://coveralls.io/r/dart-lang/linter)

 [![Pub](https://img.shields.io/pub/v/linter.svg)](https://pub.dev/packages/linter)

+[![package publisher](https://img.shields.io/pub/publisher/linter.svg)](https://pub.dev/packages/linter/publisher)

+[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/dart-lang/linter/badge)](https://api.securityscorecards.dev/projects/github.com/dart-lang/linter)

 

 ## Installing

 

@@ -34,11 +36,10 @@
 

     $ dart analyze .

     

-and see any violations of the `annotate_overrides`, `hash_and_equals`, and `prefer_is_not_empty` rules in the console.  To help you choose 

-the rules you want to enable for your package, we have provided a [complete list of rules][lints] with lints recommended by the Dart team

-collected in [`package:lints`][package-dart-lints]. For the lints that are enforced internally at Google, see [`package:pedantic`][package-pedantic].

-For a set of rules corresponding to the [Effective Dart][effective_dart] guide, see [`package:effective_dart`][package-effective-dart].  Lints

-recommended for Flutter apps, packages, and plugins are documented in [`package:flutter_lints`][package-flutter-lints].

+and see any violations of the `annotate_overrides`, `hash_and_equals`, and `prefer_is_not_empty` rules in the console.

+To help you choose the rules you want to enable for your package, we have provided a [complete list of rules][lints]

+with lints recommended by the Dart team collected in [`package:lints`][package-dart-lints]. Lints recommended for Flutter apps, packages,

+and plugins are documented in [`package:flutter_lints`][package-flutter-lints].

 

 If a specific lint warning should be ignored, it can be flagged with a comment.  For example, 

 

@@ -84,20 +85,18 @@
 ## Contributing

 

 Feedback is greatly appreciated and contributions are welcome! Please read the

-[contribution guidelines](CONTRIBUTING.md); mechanics of writing lints are covered [here](doc/WritingLints.MD).

+[contribution guidelines](CONTRIBUTING.md); mechanics of writing lints are covered [here](doc/writing-lints.md).

 

 ## Features and bugs

 

 Please file feature requests and bugs in the [issue tracker][tracker].

 

-[analyzer_cli]: https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli#dartanalyzer

+[analyzer_cli]: https://dart.dev/tools/dart-analyze

 [dart_cli]: https://dart.dev/tools/dart-tool

 [effective_dart]:https://dart.dev/guides/language/effective-dart

 [lints]: https://dart-lang.github.io/linter/lints/

 [options_file]: https://dart.dev/guides/language/analysis-options#the-analysis-options-file

-[package-effective-dart]: https://github.com/tenhobi/effective_dart

 [package-dart-lints]: https://github.com/dart-lang/lints

-[package-flutter-lints]: https://github.com/flutter/packages/tree/master/packages/flutter_lints

-[package-pedantic]: https://github.com/dart-lang/pedantic/blob/master/lib/analysis_options.yaml

+[package-flutter-lints]: https://github.com/flutter/packages/tree/main/packages/flutter_lints

 [style_guide]:https://dart.dev/guides/language/effective-dart/style/

 [tracker]: https://github.com/dart-lang/linter/issues

diff --git a/analysis_options.yaml b/analysis_options.yaml
index ae15c9c..b64e28c 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -36,10 +36,11 @@
     - cancel_subscriptions
 #    - cascade_invocations
     - cast_nullable_to_non_nullable
+#    - combinators_ordering
 #    - close_sinks # https://github.com/dart-lang/linter/issues/268
     - comment_references
     - directives_ordering
-    - invariant_booleans
+    - discarded_futures
     - join_return_with_assignment
 #    - lines_longer_than_80_chars # under review (see #1068)
     - literal_only_boolean_expressions
@@ -58,11 +59,10 @@
     - prefer_expression_function_bodies
 #    - prefer_final_locals
     - prefer_foreach
-    - prefer_interpolation_to_compose_strings
 #    - prefer_mixin
     - prefer_null_aware_method_calls
     - prefer_relative_imports
-    - depend_on_referenced_packages
+    - prefer_single_quotes
 #    - public_member_api_docs
 #    - sort_constructors_first
     - sort_pub_dependencies
@@ -73,13 +73,12 @@
     - unawaited_futures # pedantic
     - unnecessary_final
     - unnecessary_lambdas
-    - unnecessary_null_aware_assignments
     - unnecessary_null_checks
-    - unnecessary_nullable_for_final_variable_declarations
     - unnecessary_parenthesis
     - unnecessary_statements
     - use_if_null_to_convert_nulls_to_bools
     - use_late_for_private_fields_and_variables
     - use_setters_to_change_properties
     - use_string_buffers
+    - use_super_parameters
     - use_to_and_as_if_applicable
diff --git a/doc/RELEASING.md b/doc/RELEASING.md
deleted file mode 100644
index 6dbc9f2..0000000
--- a/doc/RELEASING.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Pushing a new Release
-
-## Preparing a Release
-
-Before releasing there are a few boxes to tick off.
-
-* [ ] Is there a [milestone plan](https://github.com/dart-lang/linter/issues?q=is%3Aopen+is%3Aissue+label%3Amilestone-plan) for the release? If so, has it been updated?
-* [ ] Is the changelog up to date? (Look at commit history to verify.)
-* [ ] Does the `AUTHORS` file need updating?
-* [ ] Spot check new lint rules for [naming consistency](https://github.com/dart-lang/linter/blob/master/doc/WritingLints.MD).  Rename as needed.
-
-## Doing the Push
-
-First, make sure the build is GREEN.
-
-[![Build Status](https://github.com/dart-lang/linter/workflows/linter/badge.svg)](https://github.com/dart-lang/linter/actions)
-
-All clear?  Then:
-
-  1. Update `pubspec.yaml` with a version bump and `CHANGELOG.md` accordingly.
-  2. Tag a release [branch](https://github.com/dart-lang/linter/releases).
-  3. Publish to `pub.dartlang` (`pub --publish`); heed all warnings!
-  4. On the `io` branch, regenerate linter docs (`dart tool/doc.dart --out path/to/io_linter/lints`).
-  5. Update SDK `DEPS`.
-
-You're done!
diff --git a/doc/WritingLints.MD b/doc/WritingLints.MD
deleted file mode 100644
index 52fbcb7..0000000
--- a/doc/WritingLints.MD
+++ /dev/null
@@ -1,122 +0,0 @@
-# Writing Lints
-
-Preliminary notes on writing lints.
-
-## Lint Criteria
-
-Borrowing heavily from the criteria for [adding new checks to errorprone](https://github.com/google/error-prone/wiki/Criteria-for-new-checks), 
-lints should have the following properties.
-
-Dart lints:
-
-* should be easy to understand. The problem should be obvious once the linter points it out.
-* should have a correspondingly easy fix. For example, "Remove this type annotation", or "Delete these braces", not 
-"Introduce a new subclass and override methods A, B, and C."
-* should have *few* false positives.
-
-## Lint Properties
-
-Every lint has a:
-
-**Name.** A short name using Dart package naming conventions.  Naming is *hard* but strive to be concise and consistent.  Where possible, use existing rules for inspiration and observe the rules of [parallel construction](https://en.wikipedia.org/wiki/Parallelism_(grammar)).
-
-**Description.** A short description of the lint, suitable for printing in console output.  For example:
-
-```
-[lint] DO name types using UpperCamelCase.
-```
-
-**Kind.** The first word in the description should identify the *kind* of lint where kinds are derived from the 
-[style guide](https://dart.dev/guides/language/effective-dart/style/). In summary:
-
-* ***DO*** guidelines describe practices that should always be followed. There will almost never be a valid reason 
-to stray from them.
-
-* ***DON'T*** guidelines are the converse: things that are almost never a good idea. You'll note there are few of 
-these here. Guidelines like these in other languages help to avoid the pitfalls that appear over time. Dart is 
-new enough that we can just fix those pitfalls directly instead of putting up ropes around them.
-
-* ***PREFER*** guidelines are practices that you should follow. However, there may be circumstances where it makes 
-sense to do otherwise. Just make sure you understand the full implications of ignoring the guideline when you do.
-
-* ***AVOID*** guidelines are the dual to "prefer": stuff you shouldn't do but where there may be good reasons to 
-on rare occasions.
-
-* ***CONSIDER*** guidelines are practices that you might or might not want to follow, depending on circumstances, 
-precedents, and your own preference.
-
-**Detailed Description.** In addition to the short description, a lint rule should have more detailed rationale 
-with code examples, ideally *good* and *bad*. The [style guide](https://dart.dev/guides/language/effective-dart/style/) is 
-a great source for inspiration.  Many style recommendations have been directly translated to lints as enumerated
-[here](https://dart-lang.github.io/linter/lints/).
-
-**Group.**  A grouping.  For example, *Style Guide* aggregates style guide derived lints.
-
-**Maturity.** Rules can be further distinguished by maturity. Unqualified rules are considered stable, 
-while others may be marked *EXPERIMENTAL* or *PROPOSED* to indicate that they are under review.
-
-## Mechanics
-
-Lints live in the [lib/src/rules](https://github.com/dart-lang/linter/tree/master/lib/src/rules) directory. 
-Corresponding tests live in [test_data/rules](https://github.com/dart-lang/linter/tree/master/test_data/rules). 
-
-Rule stubs can be generated with the [rule.dart](https://github.com/dart-lang/linter/blob/master/tool/rule.dart) 
-helper script and documentation gets generated with [doc.dart](https://github.com/dart-lang/linter/blob/master/tool/doc.dart). 
-Helper scripts can be invoked via `dart` or grinder (`pub run grinder docs --dir=doc_location` and `pub run grinder rule --name=my_new_rule` respectively).  Using grinder, for example
-
-    $ pub run grinder rule --name=my_new_lint
-    
-generates lint and test stubs in `lib/src/rules` and `test_data/rules`.
-
-### Analyzer APIs
-
-The linter has a close relationship with the `analyzer` package and at times reaches into non-public APIs.  For the most part, we have isolated these references in an [analyzer.dart utility library](https://github.com/dart-lang/linter/blob/master/lib/src/analyzer.dart).  *Whereever possible please use this library to access analyzer internals.*  
-
-  * If `analyzer.dart` is missing something please consider opening an issue where we can discuss how best to add it. 
-  * If you find yourself tempted to make references to analyzer [implementation classes](https://dart-lang.github.io/linter/lints/implementation_imports.html) also consider opening an issue so that we can see how best to manage the new dependency.
-  
-Thanks!
-
-### Dart Language Specification
-
-When writing lints, it can be useful to have the [Dart Language Specification](https://dart.dev/guides/language/spec) handy.  If you're working to support bleeding edge language features, you'll want the [latest draft](https://spec.dart.dev/DartLangSpecDraft.pdf). 
-
-### Writing Tests that Depend on Dart SDK Details
-
-**Important:** when writing tests that use standard `dart:` libraries, it's important to keep in mind that linter tests use a mocked SDK that has only a small subset of the real one.  We do this for performance reasons as it's FAR faster to load a mock SDK into memory than read the real one from disk.  If you are writing tests that depend on something in the Dart SDK (for example, an interface such as `Iterable`), you may need to update SDK mock content located in the `package:analyzer` [test utilities `mock_sdk.dart`](https://github.com/dart-lang/sdk/blob/master/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart).
-
-### Running Tests
-
-The test suite run during the linter's CI, can be run locally like so:
-
-    $ dart test/all.dart
-
-alternatively, tests can be run using `pub`:
-
-    $ dart test
-    
-Running a single test can be done using the `rule_debug` helper:
-
-    $ dart test/util/rule_debug.dart always_declare_return_types
-    
-would only test `always_declare_return_types`.  (This can be very handy if you want to run tests in the VM debugger).
-
-If you simply want to verify a test, you can run it solo in `pub`:
-
-    $ dart test -N always_declare_return_types
-
-### Utilities
-
-You'll notice when authoring a new rule that failures cause the AST of the test case to be displayed to `stdout`.  If you simply want to dump the AST of a given compilation unit, you can use the `spelunk` helper directly.  For example:
-
-    $ dart tool/spelunk.dart lib/src/rules.dart
-    
-would dump the AST of `rules.dart`.
-
-### Performance
-
-For performance reasons rules should prefer implementing `NodeLintRule` and registering interest in specific AST node types using `registry.addXYZ(this, visitor)`. Avoid overriding `visitCompilationUnit()` and performing your own full `CompilationUnit` visits.
-
-# Feedback is Welcome!
-
-Details are under active development.  Feedback is most [welcome](https://github.com/dart-lang/linter/issues)!
diff --git a/doc/releasing.md b/doc/releasing.md
new file mode 100644
index 0000000..3d029ae
--- /dev/null
+++ b/doc/releasing.md
@@ -0,0 +1,44 @@
+# Pushing a new Release
+
+## Preparing a Release
+
+Before releasing there are a few boxes to tick off.
+
+* [ ] Is there a [milestone plan](https://github.com/dart-lang/linter/issues?q=is%3Aopen+is%3Aissue+label%3Amilestone-plan) for the release? If so, has it been updated?
+* [ ] Is the changelog up to date? (Look at commit history to verify.)
+* [ ] Does the `AUTHORS` file need updating?
+* [ ] Spot check new lint rules for [naming consistency](https://github.com/dart-lang/linter/blob/main/doc/writing-lints.md).  Rename as needed.
+
+## Doing the Push
+
+First, make sure the build is GREEN.
+
+[![Build Status](https://github.com/dart-lang/linter/workflows/linter/badge.svg)](https://github.com/dart-lang/linter/actions)
+
+All clear?  Then:
+
+  1. Update `pubspec.yaml` with a version bump and `CHANGELOG.md` accordingly.
+  2. Tag a release [branch](https://github.com/dart-lang/linter/releases).
+  3. Publish to `pub.dev` (`dart pub lish`); heed all warnings that are not test data related!
+  4. Update SDK `DEPS`.
+
+You're done!
+
+## Updating documentation
+
+Documentation on https://dart-lang.github.io/linter/ 
+is located on the [gh-pages branch](https://github.com/dart-lang/linter/tree/gh-pages)
+and automatically updated as changes are made to the `main` branch.
+
+To update the dart.dev
+[linter rules](https://dart.dev/tools/linter-rules) documentation,
+you need to manually update the file its generated from:
+
+  1. Download the automatically generated [`rules.json`](https://github.com/dart-lang/linter/blob/gh-pages/lints/machine/rules.json) file.
+  2. Rename the downloaded file to `linter_rules.json`.
+  3. Clone and/or open the [site-www repository](https://github.com/dart-lang/site-www),
+     creating a new branch for your changes.
+  4. Replace the old [`linter_rules.json`](https://github.com/dart-lang/site-www/blob/main/src/_data/linter_rules.json) 
+     with the newly downloaded version and commit your changes.
+  5. Submit your changes in a pull request with an overview of what has changed,
+     such as what version of the linter these changes are tied to.
diff --git a/doc/writing-lints.md b/doc/writing-lints.md
new file mode 100644
index 0000000..5007050
--- /dev/null
+++ b/doc/writing-lints.md
@@ -0,0 +1,174 @@
+# Writing Lints
+
+Preliminary notes on writing lints.
+
+## Lint Criteria
+
+Borrowing heavily from the criteria for [adding new checks to errorprone],
+lints should have the following properties.
+
+Dart lints:
+
+* should be easy to understand.  The problem should be obvious once the linter
+  points it out.
+* should have a correspondingly easy fix.  For example, "Remove this type
+  annotation", or "Delete these braces", not "Introduce a new subclass and
+  override methods A, B, and C."
+* should have *few* false positives.
+
+## Lint Properties
+
+Every lint has a:
+
+**Name.** A short name using [Dart package naming conventions].  Naming is
+*hard* but strive to be concise and consistent.  Prefer to use the _problem_ as
+the name, as in the existing lints `control_flow_in_finally` and
+`empty_catches`.  Do not start a lint's name with "always", "avoid", or
+"prefer".  Where possible, use existing rules for inspiration and observe the
+rules of [parallel construction].
+
+**Description.** A short description of the lint, suitable for printing in
+console output.  For example:
+
+```
+[lint] DO name types using UpperCamelCase.
+```
+
+**Kind.** The first word in the description should identify the *kind* of lint
+where kinds are derived from the [style guide]. In summary:
+
+* ***DO*** guidelines describe practices that should always be followed.  There
+will almost never be a valid reason to stray from them.
+
+* ***DON'T*** guidelines are the converse: things that are almost never a good
+idea.  You'll note there are few of these here.  Guidelines like these in other
+languages help to avoid the pitfalls that appear over time.  Dart is new enough
+that we can just fix those pitfalls directly instead of putting up ropes around
+them.
+
+* ***PREFER*** guidelines are practices that you should follow.  However, there
+may be circumstances where it makes sense to do otherwise.  Just make sure you
+understand the full implications of ignoring the guideline when you do.
+
+* ***AVOID*** guidelines are the dual to "prefer": stuff you shouldn't do but
+where there may be good reasons to on rare occasions.
+
+* ***CONSIDER*** guidelines are practices that you might or might not want to
+follow, depending on circumstances, precedents, and your own preference.
+
+**Detailed Description.** In addition to the short description, a lint rule
+should have more detailed rationale with code examples, ideally *good* and
+*bad*.  The [style guide] is a great source for inspiration.  Many style
+recommendations have been directly translated to lints as enumerated
+[here][lints].
+
+**Group.**  A grouping.  For example, *Style Guide* aggregates style guide
+derived lints.
+
+**Maturity.** Rules can be further distinguished by maturity.  Unqualified rules
+are considered stable, while others may be marked *EXPERIMENTAL* or *PROPOSED*
+to indicate that they are under review.
+
+## Mechanics
+
+Lints live in the [lib/src/rules] directory. Corresponding tests live in
+[test_data/rules]. 
+
+Rule stubs can be generated with the [rule.dart] helper script and documentation
+gets generated with [doc.dart].  Helper scripts can be invoked via `dart` or
+grinder (`dart run grinder docs --dir=doc_location` and
+`dart run grinder rule --name=my_new_rule` respectively).  Using grinder, for
+example
+
+    $ dart run grinder rule --name=my_new_lint
+    
+generates lint and test stubs in `lib/src/rules` and `test_data/rules`.
+
+### Analyzer APIs
+
+The linter has a close relationship with the `analyzer` package and at times
+reaches into non-public APIs.  For the most part, we have isolated these
+references in an [analyzer.dart utility library].  *Whereever possible please
+use this library to access analyzer internals.*  
+
+  * If `analyzer.dart` is missing something please consider opening an issue
+    where we can discuss how best to add it. 
+  * If you find yourself tempted to make references to analyzer
+    [implementation classes][implementation_imports] also consider opening an
+    issue so that we can see how best to manage the new dependency.
+  
+Thanks!
+
+### Dart Language Specification
+
+When writing lints, it can be useful to have the [Dart Language Specification]
+handy.  If you're working to support bleeding edge language features, you'll
+want the [latest draft][draft language spec]. 
+
+### Writing Tests that Depend on Dart SDK Details
+
+**Important:** when writing tests that use standard `dart:` libraries, it's
+important to keep in mind that linter tests use a mocked SDK that has only a
+small subset of the real one.  We do this for performance reasons as it's FAR
+faster to load a mock SDK into memory than read the real one from disk.  If you
+are writing tests that depend on something in the Dart SDK (for example, an
+interface such as `Iterable`), you may need to update SDK mock content located
+in the `package:analyzer` [test utilities `mock_sdk.dart`][mock_sdk.dart].
+
+### Running Tests
+
+The test suite run during the linter's CI, can be run locally like so:
+
+    $ dart test/all.dart
+
+alternatively, tests can be run using `pub`:
+
+    $ dart test
+    
+Running a single test can be done using the `rule_debug` helper:
+
+    $ dart test/util/rule_debug.dart always_declare_return_types
+    
+would only test `always_declare_return_types`.  (This can be very handy if you
+want to run tests in the VM debugger).
+
+If you simply want to verify a test, you can run it solo in `pub`:
+
+    $ dart test -N always_declare_return_types
+
+### Utilities
+
+You'll notice when authoring a new rule that failures cause the AST of the test
+case to be displayed to `stdout`.  If you simply want to dump the AST of a given
+compilation unit, you can use the `spelunk` helper directly.  For example:
+
+    $ dart tool/spelunk.dart lib/src/rules.dart
+    
+would dump the AST of `rules.dart`.
+
+### Performance
+
+For performance reasons rules should prefer implementing `NodeLintRule` and
+registering interest in specific AST node types using
+`registry.addXYZ(this, visitor)`.  Avoid overriding `visitCompilationUnit()` and
+performing your own full `CompilationUnit` visits.
+
+# Feedback is Welcome!
+
+Details are under active development.  Feedback is most [welcome][issues]!
+
+[adding new checks to errorprone]: https://github.com/google/error-prone/wiki/Criteria-for-new-checks
+[Dart package naming conventions]: https://dart.dev/tools/pub/pubspec#name
+[parallel construction]: https://en.wikipedia.org/wiki/Parallelism_(grammar)
+[style guide]: https://dart.dev/guides/language/effective-dart/style/
+[lints]: https://dart-lang.github.io/linter/lints/
+[lib/src/rules]: https://github.com/dart-lang/linter/tree/main/lib/src/rules
+[test_data/rules]: https://github.com/dart-lang/linter/tree/main/test_data/rules
+[rule.dart]: https://github.com/dart-lang/linter/blob/main/tool/rule.dart
+[doc.dart]: https://github.com/dart-lang/linter/blob/main/tool/doc.dart
+[analyzer.dart utility library]: https://github.com/dart-lang/linter/blob/main/lib/src/analyzer.dart
+[implementation_imports]: https://dart-lang.github.io/linter/lints/implementation_imports.html
+[Dart Language Specification]: https://dart.dev/guides/language/spec
+[draft language spec]: https://spec.dart.dev/DartLangSpecDraft.pdf
+[mock_sdk.dart]: https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+[issues]: https://github.com/dart-lang/linter/issues
\ No newline at end of file
diff --git a/example/all.yaml b/example/all.yaml
index f8741cd..1b60a01 100644
--- a/example/all.yaml
+++ b/example/all.yaml
@@ -20,6 +20,7 @@
     - avoid_equals_and_hash_code_on_mutable_classes
     - avoid_escaping_inner_quotes
     - avoid_field_initializers_in_const_classes
+    - avoid_final_parameters
     - avoid_function_literals_in_foreach_calls
     - avoid_implementing_value_types
     - avoid_init_to_null
@@ -55,28 +56,35 @@
     - cascade_invocations
     - cast_nullable_to_non_nullable
     - close_sinks
+    - collection_methods_unrelated_type
+    - combinators_ordering
     - comment_references
+    - conditional_uri_does_not_exist
     - constant_identifier_names
     - control_flow_in_finally
     - curly_braces_in_flow_control_structures
+    - dangling_library_doc_comments
     - depend_on_referenced_packages
     - deprecated_consistency
     - diagnostic_describe_all_properties
     - directives_ordering
+    - discarded_futures
     - do_not_use_environment
     - empty_catches
     - empty_constructor_bodies
     - empty_statements
+    - enable_null_safety
     - eol_at_end_of_file
     - exhaustive_cases
     - file_names
     - flutter_style_todos
     - hash_and_equals
     - implementation_imports
-    - invariant_booleans
+    - implicit_call_tearoffs
     - iterable_contains_unrelated_type
     - join_return_with_assignment
     - leading_newlines_in_multiline_strings
+    - library_annotations
     - library_names
     - library_prefixes
     - library_private_types_in_public_api
@@ -87,6 +95,8 @@
     - no_adjacent_strings_in_list
     - no_default_cases
     - no_duplicate_case_values
+    - no_leading_underscores_for_library_prefixes
+    - no_leading_underscores_for_local_identifiers
     - no_logic_in_create_state
     - no_runtimeType_toString
     - non_constant_identifier_names
@@ -113,7 +123,6 @@
     - prefer_constructors_over_static_methods
     - prefer_contains
     - prefer_double_quotes
-    - prefer_equal_for_default_values
     - prefer_expression_function_bodies
     - prefer_final_fields
     - prefer_final_in_for_each
@@ -145,7 +154,9 @@
     - public_member_api_docs
     - recursive_getters
     - require_trailing_commas
+    - secure_pubspec_urls
     - sized_box_for_whitespace
+    - sized_box_shrink_expand
     - slash_for_doc_comments
     - sort_child_properties_last
     - sort_constructors_first
@@ -160,11 +171,15 @@
     - unnecessary_await_in_return
     - unnecessary_brace_in_string_interps
     - unnecessary_const
+    - unnecessary_constructor_name
     - unnecessary_final
     - unnecessary_getters_setters
     - unnecessary_lambdas
+    - unnecessary_late
+    - unnecessary_library_directive
     - unnecessary_new
     - unnecessary_null_aware_assignments
+    - unnecessary_null_aware_operator_on_extension_on_nullable
     - unnecessary_null_checks
     - unnecessary_null_in_if_null_operators
     - unnecessary_nullable_for_final_variable_declarations
@@ -175,9 +190,14 @@
     - unnecessary_string_escapes
     - unnecessary_string_interpolations
     - unnecessary_this
+    - unnecessary_to_list_in_spreads
+    - unreachable_from_main
     - unrelated_type_equality_checks
     - unsafe_html
     - use_build_context_synchronously
+    - use_colored_box
+    - use_decorated_box
+    - use_enums
     - use_full_hex_values_for_flutter_colors
     - use_function_type_syntax_for_parameters
     - use_if_null_to_convert_nulls_to_bools
@@ -189,6 +209,8 @@
     - use_rethrow_when_possible
     - use_setters_to_change_properties
     - use_string_buffers
+    - use_string_in_part_of_directives
+    - use_super_parameters
     - use_test_throws_matchers
     - use_to_and_as_if_applicable
     - valid_regexps
diff --git a/example/readme.md b/example/readme.md
index 0725df0..d728e7a 100644
--- a/example/readme.md
+++ b/example/readme.md
@@ -11,4 +11,4 @@
 [`package:lints`](https://github.com/dart-lang/lints/).
 
 Flutter team rules are maintained in
-[`package:flutter_lints`](https://github.com/flutter/packages/tree/master/packages/flutter_lints).
+[`package:flutter_lints`](https://github.com/flutter/packages/tree/main/packages/flutter_lints).
diff --git a/lib/src/analyzer.dart b/lib/src/analyzer.dart
index d2e662c..4827d5d 100644
--- a/lib/src/analyzer.dart
+++ b/lib/src/analyzer.dart
@@ -20,32 +20,25 @@
 
 export 'package:analyzer/dart/element/type_system.dart';
 export 'package:analyzer/src/dart/ast/token.dart';
-export 'package:analyzer/src/dart/constant/evaluation.dart'
-    show ConstantEvaluationEngine, ConstantVisitor;
-export 'package:analyzer/src/dart/constant/value.dart' show DartObjectImpl;
 export 'package:analyzer/src/dart/element/inheritance_manager3.dart'
     show InheritanceManager3, Name;
 export 'package:analyzer/src/dart/error/lint_codes.dart';
 export 'package:analyzer/src/dart/resolver/exit_detector.dart';
-export 'package:analyzer/src/generated/engine.dart'
-    show AnalysisContext, AnalysisErrorInfo;
+export 'package:analyzer/src/generated/engine.dart' show AnalysisErrorInfo;
 export 'package:analyzer/src/generated/source.dart' show LineInfo, Source;
 export 'package:analyzer/src/lint/linter.dart'
     show
         DartLinter,
-        LintRule,
         Group,
-        Maturity,
+        LintFilter,
+        LintRule,
         LinterContext,
         LinterOptions,
-        LintFilter,
+        Maturity,
         NodeLintRegistry,
         NodeLintRule;
-export 'package:analyzer/src/lint/project.dart'
-    show DartProject, ProjectVisitor;
-export 'package:analyzer/src/lint/pub.dart'
-    show PubspecVisitor, PSEntry, Pubspec;
-export 'package:analyzer/src/lint/util.dart' show Spelunker;
+export 'package:analyzer/src/lint/pub.dart' show PSEntry, PubspecVisitor;
+export 'package:analyzer/src/lint/util.dart' show FileSpelunker;
 export 'package:analyzer/src/services/lint.dart' show lintRegistry;
 export 'package:analyzer/src/workspace/pub.dart' show PubWorkspacePackage;
 
diff --git a/lib/src/ast.dart b/lib/src/ast.dart
index 8f1b09a..a989c33 100644
--- a/lib/src/ast.dart
+++ b/lib/src/ast.dart
@@ -4,6 +4,7 @@
 
 /// Common AST helpers.
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/syntactic_entity.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/visitor.dart';
@@ -14,6 +15,8 @@
 import 'analyzer.dart';
 import 'utils.dart';
 
+final List<String> reservedWords = _collectReservedWords();
+
 /// Returns direct children of [parent].
 List<Element> getChildren(Element parent, [String? name]) {
   var children = <Element>[];
@@ -32,17 +35,30 @@
 
 /// Returns a field identifier with the given [name] in the given [decl]'s
 /// variable declaration list or `null` if none is found.
-SimpleIdentifier? getFieldIdentifier(FieldDeclaration decl, String name) {
+Token? getFieldName(FieldDeclaration decl, String name) {
   for (var v in decl.fields.variables) {
-    if (v.name.name == name) {
+    if (v.name.lexeme == name) {
       return v.name;
     }
   }
   return null;
 }
 
+/// Returns the value of an [IntegerLiteral] or [PrefixExpression] with a
+/// minus and then an [IntegerLiteral]. If a [context] is provided,
+/// [SimpleIdentifier]s are evaluated as constants. For anything else,
+/// returns `null`.
+int? getIntValue(Expression expression, LinterContext? context) {
+  if (expression is PrefixExpression) {
+    var operand = expression.operand;
+    if (expression.operator.type != TokenType.MINUS) return null;
+    return _getIntValue(operand, context, negated: true);
+  }
+  return _getIntValue(expression, context);
+}
+
 /// Returns the most specific AST node appropriate for associating errors.
-AstNode getNodeToAnnotate(Declaration node) {
+SyntacticEntity getNodeToAnnotate(Declaration node) {
   var mostSpecific = _getNodeToAnnotate(node);
   return mostSpecific ?? node;
 }
@@ -64,26 +80,12 @@
 }
 
 /// Returns `true` if this [element] has a `@literal` annotation.
-bool hasLiteralAnnotation(Element element) {
-  var metadata = element.metadata;
-  for (var i = 0; i < metadata.length; i++) {
-    if (metadata[i].isLiteral) {
-      return true;
-    }
-  }
-  return false;
-}
+@Deprecated('prefer: element.hasLiteral')
+bool hasLiteralAnnotation(Element element) => element.hasLiteral;
 
 /// Returns `true` if this [element] has an `@override` annotation.
-bool hasOverrideAnnotation(Element element) {
-  var metadata = element.metadata;
-  for (var i = 0; i < metadata.length; i++) {
-    if (metadata[i].isOverride) {
-      return true;
-    }
-  }
-  return false;
-}
+@Deprecated('prefer: element.hasOverride')
+bool hasOverrideAnnotation(Element element) => element.hasOverride;
 
 /// Returns `true` if this [node] is the child of a private compilation unit
 /// member.
@@ -100,7 +102,7 @@
 
 /// Returns `true` if this element is the `==` method declaration.
 bool isEquals(ClassMember element) =>
-    element is MethodDeclaration && element.name.name == '==';
+    element is MethodDeclaration && element.name.lexeme == '==';
 
 /// Returns `true` if the keyword associated with this token is `final` or
 /// `const`.
@@ -108,10 +110,10 @@
     isKeyword(token, Keyword.FINAL) || isKeyword(token, Keyword.CONST);
 
 /// Returns `true` if this element is a `hashCode` method or field declaration.
-bool isHashCode(ClassMember element) =>
-    (element is MethodDeclaration && element.name.name == 'hashCode') ||
-    (element is FieldDeclaration &&
-        getFieldIdentifier(element, 'hashCode') != null);
+bool isHashCode(ClassMember element) => _hasFieldOrMethod(element, 'hashCode');
+
+/// Returns `true` if this element is an `index` method or field declaration.
+bool isIndex(ClassMember element) => _hasFieldOrMethod(element, 'index');
 
 /// Return true if this compilation unit [node] is declared within the given
 /// [package]'s `lib/` directory tree.
@@ -143,14 +145,14 @@
     token is KeywordToken && token.keyword == keyword;
 
 /// Returns `true` if the given [id] is a Dart keyword.
-bool isKeyWord(String id) => Keyword.keywords.keys.contains(id);
+bool isKeyWord(String id) => Keyword.keywords.containsKey(id);
 
 /// Returns `true` if the given [ClassMember] is a method.
 bool isMethod(ClassMember m) => m is MethodDeclaration;
 
 /// Check if the given identifier has a private name.
-bool isPrivate(SimpleIdentifier? identifier) =>
-    identifier != null ? Identifier.isPrivateName(identifier.name) : false;
+bool isPrivate(Token? name) =>
+    name != null ? Identifier.isPrivateName(name.lexeme) : false;
 
 /// Returns `true` if the given [ClassMember] is a public method.
 bool isPublicMethod(ClassMember m) {
@@ -158,6 +160,9 @@
   return declaredElement != null && isMethod(m) && declaredElement.isPublic;
 }
 
+/// Check if the given word is a Dart reserved word.
+bool isReservedWord(String word) => reservedWords.contains(word);
+
 /// Returns `true` if the given method [declaration] is a "simple getter".
 ///
 /// A simple getter takes one of these basic forms:
@@ -183,7 +188,7 @@
   } else if (body is BlockFunctionBody) {
     var block = body.block;
     if (block.statements.length == 1) {
-      var statement = block.statements[0];
+      var statement = block.statements.first;
       if (statement is ReturnStatement) {
         return _checkForSimpleGetter(declaration, statement.expression);
       }
@@ -217,7 +222,7 @@
   } else if (body is BlockFunctionBody) {
     var block = body.block;
     if (block.statements.length == 1) {
-      var statement = block.statements[0];
+      var statement = block.statements.first;
       if (statement is ExpressionStatement) {
         return _checkForSimpleSetter(setter, statement.expression);
       }
@@ -230,6 +235,9 @@
 /// Returns `true` if the given [id] is a valid Dart identifier.
 bool isValidDartIdentifier(String id) => !isKeyWord(id) && isIdentifier(id);
 
+/// Returns `true` if this element is a `values` method or field declaration.
+bool isValues(ClassMember element) => _hasFieldOrMethod(element, 'values');
+
 /// Returns `true` if the keyword associated with this token is `var`.
 bool isVar(Token token) => isKeyword(token, Keyword.VAR);
 
@@ -249,7 +257,7 @@
   var file = resourceProvider.getFile(fullName);
 
   // Look for a pubspec.yaml file.
-  for (var folder in file.parent2.withAncestors) {
+  for (var folder in file.parent.withAncestors) {
     var pubspecFile = folder.getChildAssumingFile('pubspec.yaml');
     if (pubspecFile.exists) {
       return pubspecFile;
@@ -308,14 +316,37 @@
 
     var parameters = setter.parameters?.parameters;
     if (parameters != null && parameters.length == 1) {
-      return rightElement == parameters[0].declaredElement;
+      return rightElement == parameters.first.declaredElement;
     }
   }
 
   return false;
 }
 
-AstNode? _getNodeToAnnotate(Declaration node) {
+List<String> _collectReservedWords() {
+  var reserved = <String>[];
+  for (var entry in Keyword.keywords.entries) {
+    if (entry.value.isReservedWord) {
+      reserved.add(entry.key);
+    }
+  }
+  return reserved;
+}
+
+int? _getIntValue(Expression expression, LinterContext? context,
+    {bool negated = false}) {
+  int? value;
+  if (expression is IntegerLiteral) {
+    value = expression.value;
+  } else if (expression is SimpleIdentifier && context != null) {
+    value = context.evaluateConstant(expression).value?.toIntValue();
+  }
+  if (value is! int) return null;
+
+  return negated ? -value : value;
+}
+
+SyntacticEntity? _getNodeToAnnotate(Declaration node) {
   if (node is MethodDeclaration) {
     return node.name;
   }
@@ -337,6 +368,9 @@
   if (node is EnumDeclaration) {
     return node.name;
   }
+  if (node is ExtensionDeclaration) {
+    return node.name;
+  }
   if (node is FunctionDeclaration) {
     return node.name;
   }
@@ -382,6 +416,10 @@
   return null;
 }
 
+bool _hasFieldOrMethod(ClassMember element, String name) =>
+    (element is MethodDeclaration && element.name.lexeme == name) ||
+    (element is FieldDeclaration && getFieldName(element, name) != null);
+
 /// An [Element] processor function type.
 /// If `true` is returned, children of [element] will be visited.
 typedef ElementProcessor = bool Function(Element element);
@@ -395,8 +433,18 @@
   @override
   void visitElement(Element element) {
     var visitChildren = processor(element);
-    if (visitChildren == true) {
+    if (visitChildren) {
       element.visitChildren(this);
     }
   }
 }
+
+extension ElementExtension on Element? {
+  // TODO(srawlins): Move to extensions.dart.
+  bool get isDartCorePrint {
+    var self = this;
+    return self is FunctionElement &&
+        self.name == 'print' &&
+        self.library.isDartCore;
+  }
+}
diff --git a/lib/src/cli.dart b/lib/src/cli.dart
index 06cac8b..edc7c70 100644
--- a/lib/src/cli.dart
+++ b/lib/src/cli.dart
@@ -18,7 +18,9 @@
 
 const unableToProcessExitCode = 64;
 String? getRoot(List<String> paths) =>
-    paths.length == 1 && Directory(paths[0]).existsSync() ? paths[0] : null;
+    paths.length == 1 && Directory(paths.first).existsSync()
+        ? paths.first
+        : null;
 
 bool isLinterErrorCode(int code) =>
     code == unableToProcessExitCode || code == processFileFailedExitCode;
@@ -66,6 +68,7 @@
     ..addMultiOption('rules',
         help: 'A list of lint rules to run. For example: '
             'avoid_as,annotate_overrides')
+    // TODO(srawlins): Remove this flag; it does not work any more.
     ..addOption('packages',
         help: 'Path to the package resolution configuration file, which\n'
             'supplies a mapping of package names to paths.');
@@ -120,6 +123,9 @@
   }
 
   var packageConfigFile = options['packages'] as String?;
+  packageConfigFile = packageConfigFile != null
+      ? _absoluteNormalizedPath(packageConfigFile)
+      : null;
 
   var stats = options['stats'] as bool;
   var benchmark = options['benchmark'] as bool;
@@ -133,7 +139,11 @@
 
   var filesToLint = <File>[];
   for (var path in options.rest) {
-    filesToLint.addAll(collectFiles(path));
+    filesToLint.addAll(
+      collectFiles(path)
+          .map((file) => _absoluteNormalizedPath(file.path))
+          .map(File.new),
+    );
   }
 
   if (benchmark) {
@@ -167,3 +177,10 @@
 $stack''');
   }
 }
+
+String _absoluteNormalizedPath(String path) {
+  var pathContext = PhysicalResourceProvider.INSTANCE.pathContext;
+  return pathContext.normalize(
+    pathContext.absolute(path),
+  );
+}
diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart
new file mode 100644
index 0000000..e0f41e4
--- /dev/null
+++ b/lib/src/extensions.dart
@@ -0,0 +1,401 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/constant/value.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/member.dart'; // ignore: implementation_imports
+import 'package:collection/collection.dart';
+
+import 'util/dart_type_utilities.dart';
+
+class EnumLikeClassDescription {
+  final Map<DartObject, Set<FieldElement>> _enumConstants;
+  EnumLikeClassDescription(this._enumConstants);
+
+  /// Returns a fresh map of the class's enum-like constant values.
+  Map<DartObject, Set<FieldElement>> get enumConstants => {..._enumConstants};
+}
+
+extension AstNodeExtension on AstNode {
+  Iterable<AstNode> get childNodes => childEntities.whereType<AstNode>();
+
+  /// Builds the list resulting from traversing the node in DFS and does not
+  /// include the node itself.
+  ///
+  /// It excludes the nodes for which the [excludeCriteria] returns true. If
+  /// [excludeCriteria] is not provided, all nodes are included.
+  @Deprecated(
+      'This approach is slow and slated for removal. Traversal via a standard visitor is preferred.')
+  Iterable<AstNode> traverseNodesInDFS({AstNodePredicate? excludeCriteria}) {
+    var nodes = <AstNode>{};
+    var nodesToVisit = List.of(childNodes);
+    if (excludeCriteria == null) {
+      while (nodesToVisit.isNotEmpty) {
+        var node = nodesToVisit.removeAt(0);
+        nodes.add(node);
+        nodesToVisit.insertAll(0, node.childNodes);
+      }
+    } else {
+      while (nodesToVisit.isNotEmpty) {
+        var node = nodesToVisit.removeAt(0);
+        if (excludeCriteria(node)) continue;
+        nodes.add(node);
+        nodesToVisit.insertAll(0, node.childNodes);
+      }
+    }
+
+    return nodes;
+  }
+}
+
+extension BlockExtension on Block {
+  /// Returns the last statement of this block, or `null` if this is empty.
+  ///
+  /// If the last immediate statement of this block is a [Block], recurses into
+  /// it to find the last statement.
+  Statement? get lastStatement {
+    if (statements.isEmpty) {
+      return null;
+    }
+    var lastStatement = statements.last;
+    if (lastStatement is Block) {
+      return lastStatement.lastStatement;
+    }
+    return lastStatement;
+  }
+}
+
+extension ClassElementExtension on ClassElement {
+  /// Returns an [EnumLikeClassDescription] for this if the latter is a valid
+  /// "enum-like" class.
+  ///
+  /// An enum-like class must meet the following requirements:
+  ///
+  /// * is concrete,
+  /// * has no public constructors,
+  /// * has no factory constructors,
+  /// * has two or more static const fields with the same type as the class,
+  /// * has no subclasses declared in the defining library.
+  ///
+  /// The returned [EnumLikeClassDescription]'s `enumConstantNames` contains all
+  /// of the static const fields with the same type as the class, with one
+  /// exception; any static const field which is marked `@Deprecated` and is
+  /// equal to another static const field with the same type as the class is not
+  /// included. Such a field is assumed to be deprecated in favor of the field
+  /// with equal value.
+  EnumLikeClassDescription? get asEnumLikeClass {
+    // See discussion: https://github.com/dart-lang/linter/issues/2083.
+
+    // Must be concrete.
+    if (isAbstract) {
+      return null;
+    }
+
+    // With only private non-factory constructors.
+    for (var constructor in constructors) {
+      if (!constructor.isPrivate || constructor.isFactory) {
+        return null;
+      }
+    }
+
+    var type = thisType;
+
+    // And 2 or more static const fields whose type is the enclosing class.
+    var enumConstantCount = 0;
+    var enumConstants = <DartObject, Set<FieldElement>>{};
+    for (var field in fields) {
+      // Ensure static const.
+      if (field.isSynthetic || !field.isConst || !field.isStatic) {
+        continue;
+      }
+      // Check for type equality.
+      if (field.type != type) {
+        continue;
+      }
+      var fieldValue = field.computeConstantValue();
+      if (fieldValue == null) {
+        continue;
+      }
+      enumConstantCount++;
+      enumConstants.putIfAbsent(fieldValue, () => {}).add(field);
+    }
+    if (enumConstantCount < 2) {
+      return null;
+    }
+
+    // And no subclasses in the defining library.
+    if (hasSubclassInDefiningCompilationUnit) return null;
+
+    return EnumLikeClassDescription(enumConstants);
+  }
+
+  bool get hasSubclassInDefiningCompilationUnit {
+    var compilationUnit = library.definingCompilationUnit;
+    for (var cls in compilationUnit.classes) {
+      InterfaceType? classType = cls.thisType;
+      do {
+        classType = classType?.superclass;
+        if (classType == thisType) {
+          return true;
+        }
+      } while (classType != null && !classType.isDartCoreObject);
+    }
+    return false;
+  }
+
+  bool get isEnumLikeClass => asEnumLikeClass != null;
+
+  /// Returns whether this class is exactly [otherName] declared in
+  /// [otherLibrary].
+  bool isClass(String otherName, String otherLibrary) =>
+      name == otherName && library.name == otherLibrary;
+}
+
+extension ClassMemberListExtension on List<ClassMember> {
+  MethodDeclaration? getMethod(String name) => whereType<MethodDeclaration>()
+      .firstWhereOrNull((node) => node.name.lexeme == name);
+}
+
+extension ConstructorElementExtension on ConstructorElement {
+  /// Returns whether `this` is the same element as the [className] constructor
+  /// named [constructorName] declared in [uri].
+  bool isSameAs({
+    required String uri,
+    required String className,
+    required String constructorName,
+  }) =>
+      library.name == uri &&
+      enclosingElement.name == className &&
+      name == constructorName;
+}
+
+extension DartTypeExtension on DartType? {
+  bool extendsClass(String? className, String library) {
+    var self = this;
+    if (self is InterfaceType) {
+      return _extendsClass(self, <InterfaceElement>{}, className, library);
+    }
+    return false;
+  }
+
+  bool implementsAnyInterface(Iterable<InterfaceTypeDefinition> definitions) {
+    bool isAnyInterface(InterfaceType i) =>
+        definitions.any((d) => i.isSameAs(d.name, d.library));
+
+    var self = this;
+    if (self is InterfaceType) {
+      return isAnyInterface(self) ||
+          !self.element.isSynthetic &&
+              self.element.allSupertypes.any(isAnyInterface);
+    } else {
+      return false;
+    }
+  }
+
+  bool implementsInterface(String interface, String library) {
+    var self = this;
+    if (self is! InterfaceType) {
+      return false;
+    }
+    bool predicate(InterfaceType i) => i.isSameAs(interface, library);
+    var element = self.element;
+    return predicate(self) ||
+        !element.isSynthetic && element.allSupertypes.any(predicate);
+  }
+
+  /// Returns whether `this` is the same element as [interface], declared in
+  /// [library].
+  bool isSameAs(String? interface, String? library) {
+    var self = this;
+    return self is InterfaceType &&
+        self.element.name == interface &&
+        self.element.library.name == library;
+  }
+
+  static bool _extendsClass(
+          InterfaceType? type,
+          Set<InterfaceElement> seenElements,
+          String? className,
+          String? library) =>
+      type != null &&
+      seenElements.add(type.element) &&
+      (type.isSameAs(className, library) ||
+          _extendsClass(type.superclass, seenElements, className, library));
+}
+
+extension ElementExtension on Element {
+  Element get canonicalElement {
+    var self = this;
+    if (self is PropertyAccessorElement) {
+      var variable = self.variable;
+      if (variable is FieldMember) {
+        // A field element defined in a parameterized type where the values of
+        // the type parameters are known.
+        //
+        // This concept should be invisible when comparing FieldElements, but a
+        // bug in the analyzer causes FieldElements to not evaluate as
+        // equivalent to equivalent FieldMembers. See
+        // https://github.com/dart-lang/sdk/issues/35343.
+        return variable.declaration;
+      } else {
+        return variable;
+      }
+    } else {
+      return self;
+    }
+  }
+}
+
+extension ExpressionExtension on Expression? {
+  bool get isNullLiteral => this?.unParenthesized is NullLiteral;
+}
+
+extension InterfaceElementExtension on InterfaceElement {
+  /// Returns whether this element is exactly [otherName] declared in
+  /// [otherLibrary].
+  bool isClass(String otherName, String otherLibrary) =>
+      name == otherName && library.name == otherLibrary;
+}
+
+extension InterfaceTypeExtension on InterfaceType {
+  /// Returns the collection of all interfaces that this type implements,
+  /// including itself.
+  Iterable<InterfaceType> get implementedInterfaces {
+    void searchSupertypes(
+        InterfaceType? type,
+        Set<InterfaceElement> alreadyVisited,
+        List<InterfaceType> interfaceTypes) {
+      if (type == null || !alreadyVisited.add(type.element)) {
+        return;
+      }
+      interfaceTypes.add(type);
+      searchSupertypes(type.superclass, alreadyVisited, interfaceTypes);
+      for (var interface in type.interfaces) {
+        searchSupertypes(interface, alreadyVisited, interfaceTypes);
+      }
+      for (var mixin in type.mixins) {
+        searchSupertypes(mixin, alreadyVisited, interfaceTypes);
+      }
+    }
+
+    var interfaceTypes = <InterfaceType>[];
+    searchSupertypes(this, {}, interfaceTypes);
+    return interfaceTypes;
+  }
+}
+
+extension MethodDeclarationExtension on MethodDeclaration {
+  bool get hasInheritedMethod => lookUpInheritedMethod() != null;
+
+  /// Returns whether this method is an override of a method in any supertype.
+  bool get isOverride {
+    var name = declaredElement?.name;
+    if (name == null) {
+      return false;
+    }
+    var parentElement = declaredElement?.enclosingElement;
+    if (parentElement is! InterfaceElement) {
+      return false;
+    }
+    var parentLibrary = parentElement.library;
+
+    if (isGetter) {
+      // Search supertypes for a getter of the same name.
+      return parentElement.allSupertypes
+          .any((t) => t.lookUpGetter2(name, parentLibrary) != null);
+    } else if (isSetter) {
+      // Search supertypes for a setter of the same name.
+      return parentElement.allSupertypes
+          .any((t) => t.lookUpSetter2(name, parentLibrary) != null);
+    } else {
+      // Search supertypes for a method of the same name.
+      return parentElement.allSupertypes
+          .any((t) => t.lookUpMethod2(name, parentLibrary) != null);
+    }
+  }
+
+  PropertyAccessorElement? lookUpGetter() {
+    var declaredElement = this.declaredElement;
+    if (declaredElement == null) {
+      return null;
+    }
+    var parent = declaredElement.enclosingElement;
+    if (parent is InterfaceElement) {
+      return parent.lookUpGetter(name.lexeme, declaredElement.library);
+    }
+    if (parent is ExtensionElement) {
+      return parent.getGetter(name.lexeme);
+    }
+    return null;
+  }
+
+  PropertyAccessorElement? lookUpInheritedConcreteGetter() {
+    var declaredElement = this.declaredElement;
+    if (declaredElement == null) {
+      return null;
+    }
+    var parent = declaredElement.enclosingElement;
+    if (parent is InterfaceElement) {
+      return parent.lookUpInheritedConcreteGetter(
+          name.lexeme, declaredElement.library);
+    }
+    // Extensions don't inherit.
+    return null;
+  }
+
+  MethodElement? lookUpInheritedConcreteMethod() {
+    var declaredElement = this.declaredElement;
+    if (declaredElement != null) {
+      var parent = declaredElement.enclosingElement;
+      if (parent is InterfaceElement) {
+        return parent.lookUpInheritedConcreteMethod(
+            name.lexeme, declaredElement.library);
+      }
+    }
+    // Extensions don't inherit.
+    return null;
+  }
+
+  PropertyAccessorElement? lookUpInheritedConcreteSetter() {
+    var declaredElement = this.declaredElement;
+    if (declaredElement != null) {
+      var parent = declaredElement.enclosingElement;
+      if (parent is InterfaceElement) {
+        return parent.lookUpInheritedConcreteSetter(
+            name.lexeme, declaredElement.library);
+      }
+    }
+    // Extensions don't inherit.
+    return null;
+  }
+
+  MethodElement? lookUpInheritedMethod() {
+    var declaredElement = this.declaredElement;
+    if (declaredElement != null) {
+      var parent = declaredElement.enclosingElement;
+      if (parent is InterfaceElement) {
+        return parent.lookUpInheritedMethod(
+            name.lexeme, declaredElement.library);
+      }
+    }
+    return null;
+  }
+}
+
+extension NullableAstNodeExtension on AstNode? {
+  Element? get canonicalElement {
+    var self = this;
+    if (self is Expression) {
+      var node = self.unParenthesized;
+      if (node is Identifier) {
+        return node.staticElement?.canonicalElement;
+      } else if (node is PropertyAccess) {
+        return node.propertyName.staticElement?.canonicalElement;
+      }
+    }
+    return null;
+  }
+}
diff --git a/lib/src/formatter.dart b/lib/src/formatter.dart
index 0caef27..ee04e4a 100644
--- a/lib/src/formatter.dart
+++ b/lib/src/formatter.dart
@@ -8,7 +8,7 @@
 import 'package:analyzer/error/error.dart';
 
 import 'analyzer.dart';
-import 'util/charcodes.dart' show $pipe, $backslash;
+import 'util/charcodes.dart' show $backslash, $pipe;
 import 'util/score_utils.dart';
 
 // Number of times to perform linting to get stable benchmarks.
@@ -60,7 +60,6 @@
   var coreRuleset = await coreRules;
   var recommendedRuleset = await recommendedRules;
   var flutterRuleset = await flutterRules;
-  var pedanticRuleset = await pedanticRules;
 
   var stats = timings.keys.map((t) {
     var sets = <String>[];
@@ -73,9 +72,6 @@
     if (flutterRuleset.contains(t)) {
       sets.add('flutter');
     }
-    if (pedanticRuleset.contains(t)) {
-      sets.add('pedantic');
-    }
 
     var details = sets.isEmpty ? '' : " [${sets.join(', ')}]";
     return _Stat('$t$details', timings[t] ?? 0);
@@ -114,8 +110,6 @@
   timings.sort();
   for (var stat in timings) {
     totalTime += stat.elapsed;
-    // TODO: Shame timings slower than 100ms?
-    // TODO: Present both total times and time per count?
     out.writeln(
         '${stat.name.padRight(longestName)}${stat.elapsed.toString().padLeft(pad)}');
   }
@@ -127,21 +121,13 @@
 }
 
 class DetailedReporter extends SimpleFormatter {
-  DetailedReporter(
-      Iterable<AnalysisErrorInfo> errors, LintFilter? filter, IOSink out,
-      {int? fileCount,
-      int? elapsedMs,
-      String? fileRoot,
-      bool showStatistics = false,
-      bool machineOutput = false,
-      bool quiet = false})
-      : super(errors, filter, out,
-            fileCount: fileCount,
-            fileRoot: fileRoot,
-            elapsedMs: elapsedMs,
-            showStatistics: showStatistics,
-            machineOutput: machineOutput,
-            quiet: quiet);
+  DetailedReporter(super.errors, super.filter, super.out,
+      {super.fileCount,
+      super.elapsedMs,
+      super.fileRoot,
+      super.showStatistics,
+      super.machineOutput,
+      super.quiet});
 
   @override
   void writeLint(AnalysisError error, {int? offset, int? line, int? column}) {
diff --git a/lib/src/rules.dart b/lib/src/rules.dart
index af930d8..9183371 100644
--- a/lib/src/rules.dart
+++ b/lib/src/rules.dart
@@ -22,6 +22,7 @@
 import 'rules/avoid_equals_and_hash_code_on_mutable_classes.dart';
 import 'rules/avoid_escaping_inner_quotes.dart';
 import 'rules/avoid_field_initializers_in_const_classes.dart';
+import 'rules/avoid_final_parameters.dart';
 import 'rules/avoid_function_literals_in_foreach_calls.dart';
 import 'rules/avoid_implementing_value_types.dart';
 import 'rules/avoid_init_to_null.dart';
@@ -57,28 +58,36 @@
 import 'rules/cascade_invocations.dart';
 import 'rules/cast_nullable_to_non_nullable.dart';
 import 'rules/close_sinks.dart';
+import 'rules/collection_methods_unrelated_type.dart';
+import 'rules/combinators_ordering.dart';
 import 'rules/comment_references.dart';
+import 'rules/conditional_uri_does_not_exist.dart';
 import 'rules/constant_identifier_names.dart';
 import 'rules/control_flow_in_finally.dart';
 import 'rules/curly_braces_in_flow_control_structures.dart';
+import 'rules/dangling_library_doc_comments.dart';
 import 'rules/depend_on_referenced_packages.dart';
 import 'rules/deprecated_consistency.dart';
 import 'rules/diagnostic_describe_all_properties.dart';
 import 'rules/directives_ordering.dart';
+import 'rules/discarded_futures.dart';
 import 'rules/do_not_use_environment.dart';
 import 'rules/empty_catches.dart';
 import 'rules/empty_constructor_bodies.dart';
 import 'rules/empty_statements.dart';
+import 'rules/enable_null_safety.dart';
 import 'rules/eol_at_end_of_file.dart';
 import 'rules/exhaustive_cases.dart';
 import 'rules/file_names.dart';
 import 'rules/flutter_style_todos.dart';
 import 'rules/hash_and_equals.dart';
 import 'rules/implementation_imports.dart';
+import 'rules/implicit_call_tearoffs.dart';
 import 'rules/invariant_booleans.dart';
 import 'rules/iterable_contains_unrelated_type.dart';
 import 'rules/join_return_with_assignment.dart';
 import 'rules/leading_newlines_in_multiline_strings.dart';
+import 'rules/library_annotations.dart';
 import 'rules/library_names.dart';
 import 'rules/library_prefixes.dart';
 import 'rules/library_private_types_in_public_api.dart';
@@ -89,6 +98,8 @@
 import 'rules/no_adjacent_strings_in_list.dart';
 import 'rules/no_default_cases.dart';
 import 'rules/no_duplicate_case_values.dart';
+import 'rules/no_leading_underscores_for_library_prefixes.dart';
+import 'rules/no_leading_underscores_for_local_identifiers.dart';
 import 'rules/no_logic_in_create_state.dart';
 import 'rules/no_runtimeType_toString.dart';
 import 'rules/non_constant_identifier_names.dart';
@@ -145,11 +156,13 @@
 import 'rules/prefer_void_to_null.dart';
 import 'rules/provide_deprecation_message.dart';
 import 'rules/pub/package_names.dart';
+import 'rules/pub/secure_pubspec_urls.dart';
 import 'rules/pub/sort_pub_dependencies.dart';
 import 'rules/public_member_api_docs.dart';
 import 'rules/recursive_getters.dart';
 import 'rules/require_trailing_commas.dart';
 import 'rules/sized_box_for_whitespace.dart';
+import 'rules/sized_box_shrink_expand.dart';
 import 'rules/slash_for_doc_comments.dart';
 import 'rules/sort_child_properties_last.dart';
 import 'rules/sort_constructors_first.dart';
@@ -164,11 +177,15 @@
 import 'rules/unnecessary_await_in_return.dart';
 import 'rules/unnecessary_brace_in_string_interps.dart';
 import 'rules/unnecessary_const.dart';
+import 'rules/unnecessary_constructor_name.dart';
 import 'rules/unnecessary_final.dart';
 import 'rules/unnecessary_getters_setters.dart';
 import 'rules/unnecessary_lambdas.dart';
+import 'rules/unnecessary_late.dart';
+import 'rules/unnecessary_library_directive.dart';
 import 'rules/unnecessary_new.dart';
 import 'rules/unnecessary_null_aware_assignments.dart';
+import 'rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart';
 import 'rules/unnecessary_null_checks.dart';
 import 'rules/unnecessary_null_in_if_null_operators.dart';
 import 'rules/unnecessary_nullable_for_final_variable_declarations.dart';
@@ -179,9 +196,14 @@
 import 'rules/unnecessary_string_escapes.dart';
 import 'rules/unnecessary_string_interpolations.dart';
 import 'rules/unnecessary_this.dart';
+import 'rules/unnecessary_to_list_in_spreads.dart';
+import 'rules/unreachable_from_main.dart';
 import 'rules/unrelated_type_equality_checks.dart';
 import 'rules/unsafe_html.dart';
 import 'rules/use_build_context_synchronously.dart';
+import 'rules/use_colored_box.dart';
+import 'rules/use_decorated_box.dart';
+import 'rules/use_enums.dart';
 import 'rules/use_full_hex_values_for_flutter_colors.dart';
 import 'rules/use_function_type_syntax_for_parameters.dart';
 import 'rules/use_if_null_to_convert_nulls_to_bools.dart';
@@ -193,6 +215,8 @@
 import 'rules/use_rethrow_when_possible.dart';
 import 'rules/use_setters_to_change_properties.dart';
 import 'rules/use_string_buffers.dart';
+import 'rules/use_string_in_part_of_directives.dart';
+import 'rules/use_super_parameters.dart';
 import 'rules/use_test_throws_matchers.dart';
 import 'rules/use_to_and_as_if_applicable.dart';
 import 'rules/valid_regexps.dart';
@@ -202,6 +226,7 @@
   Analyzer.facade.cacheLinterVersion();
   Analyzer.facade
     ..register(AlwaysDeclareReturnTypes())
+    ..register(UnnecessaryLibraryDirective())
     ..register(AlwaysPutControlBodyOnNewLine())
     ..register(AlwaysPutRequiredNamedParametersFirst())
     ..register(AlwaysRequireNonNullNamedParameters())
@@ -219,6 +244,7 @@
     ..register(AvoidEmptyElse())
     ..register(AvoidEscapingInnerQuotes())
     ..register(AvoidFieldInitializersInConstClasses())
+    ..register(AvoidFinalParameters())
     ..register(AvoidFunctionLiteralInForeachMethod())
     ..register(AvoidImplementingValueTypes())
     ..register(AvoidInitToNull())
@@ -255,28 +281,36 @@
     ..register(CascadeInvocations())
     ..register(CastNullableToNonNullable())
     ..register(CloseSinks())
+    ..register(CollectionMethodsUnrelatedType())
+    ..register(CombinatorsOrdering())
     ..register(CommentReferences())
+    ..register(ConditionalUriDoesNotExist())
     ..register(ConstantIdentifierNames())
     ..register(ControlFlowInFinally())
     ..register(CurlyBracesInFlowControlStructures())
+    ..register(DanglingLibraryDocComments())
     ..register(DependOnReferencedPackages())
     ..register(DeprecatedConsistency())
     ..register(DiagnosticsDescribeAllProperties())
     ..register(DirectivesOrdering())
+    ..register(DiscardedFutures())
     ..register(DoNotUseEnvironment())
     ..register(EmptyCatches())
     ..register(EmptyConstructorBodies())
     ..register(EmptyStatements())
+    ..register(EnableNullSafety())
     ..register(EolAtEndOfFile())
     ..register(ExhaustiveCases())
     ..register(FileNames())
     ..register(FlutterStyleTodos())
     ..register(HashAndEquals())
     ..register(ImplementationImports())
+    ..register(ImplicitCallTearoffs())
     ..register(InvariantBooleans())
     ..register(IterableContainsUnrelatedType())
     ..register(JoinReturnWithAssignment())
     ..register(LeadingNewlinesInMultilineStrings())
+    ..register(LibraryAnnotations())
     ..register(LibraryNames())
     ..register(LibraryPrefixes())
     ..register(LibraryPrivateTypesInPublicAPI())
@@ -288,6 +322,8 @@
     ..register(NoDefaultCases())
     ..register(NoDuplicateCaseValues())
     ..register(NonConstantIdentifierNames())
+    ..register(NoLeadingUnderscoresForLibraryPrefixes())
+    ..register(NoLeadingUnderscoresForLocalIdentifiers())
     ..register(NoLogicInCreateState())
     ..register(NoopPrimitiveOperations())
     ..register(NoRuntimeTypeToString())
@@ -344,9 +380,11 @@
     ..register(ProvideDeprecationMessage())
     ..register(PublicMemberApiDocs())
     ..register(PubPackageNames())
+    ..register(SecurePubspecUrls())
     ..register(RecursiveGetters())
     ..register(RequireTrailingCommas())
     ..register(SizedBoxForWhitespace())
+    ..register(SizedBoxShrinkExpand())
     ..register(SlashForDocComments())
     ..register(SortChildPropertiesLast())
     ..register(SortConstructorsFirst())
@@ -362,6 +400,7 @@
     ..register(UnnecessaryAwaitInReturn())
     ..register(UnnecessaryBraceInStringInterps())
     ..register(UnnecessaryConst())
+    ..register(UnnecessaryConstructorName())
     ..register(UnnecessaryFinal())
     ..register(UnnecessaryNew())
     ..register(UnnecessaryNullAwareAssignments())
@@ -370,7 +409,9 @@
     //..register(UnnecessaryGetters())
     ..register(UnnecessaryGettersSetters())
     ..register(UnnecessaryLambdas())
+    ..register(UnnecessaryLate())
     ..register(UnnecessaryNullableForFinalVariableDeclarations())
+    ..register(UnnecessaryNullAwareOperatorOnExtensionOnNullable())
     ..register(UnnecessaryNullChecks())
     ..register(UnnecessaryOverrides())
     ..register(UnnecessaryParenthesis())
@@ -379,9 +420,14 @@
     ..register(UnnecessaryStringEscapes())
     ..register(UnnecessaryStringInterpolations())
     ..register(UnnecessaryThis())
+    ..register(UnnecessaryToListInSpreads())
+    ..register(UnreachableFromMain())
     ..register(UnrelatedTypeEqualityChecks())
     ..register(UnsafeHtml())
     ..register(UseBuildContextSynchronously(inTestMode: inTestMode))
+    ..register(UseColoredBox())
+    ..register(UseDecoratedBox())
+    ..register(UseEnums())
     ..register(UseFullHexValuesForFlutterColors())
     ..register(UseFunctionTypeSyntaxForParameters())
     ..register(UseIfNullToConvertNullsToBools())
@@ -393,6 +439,8 @@
     ..register(UseRawStrings())
     ..register(UseSettersToChangeAProperty())
     ..register(UseStringBuffers())
+    ..register(UseStringInPartOfDirectives())
+    ..register(UseSuperParameters())
     ..register(UseTestThrowsMatchers())
     ..register(UseToAndAsIfApplicable())
     ..register(ValidRegExps())
diff --git a/lib/src/rules/always_declare_return_types.dart b/lib/src/rules/always_declare_return_types.dart
index 847af7b..58416d6 100644
--- a/lib/src/rules/always_declare_return_types.dart
+++ b/lib/src/rules/always_declare_return_types.dart
@@ -11,7 +11,6 @@
 const _desc = r'Declare method return types.';
 
 const _details = r'''
-
 **DO** declare method return types.
 
 When declaring a method or function *always* specify a return type.
@@ -45,7 +44,11 @@
 
 ''';
 
-class AlwaysDeclareReturnTypes extends LintRule implements NodeLintRule {
+class AlwaysDeclareReturnTypes extends LintRule {
+  static const LintCode code = LintCode(
+      'always_declare_return_types', 'Missing return type on method.',
+      correctionMessage: 'Try adding a return type.');
+
   AlwaysDeclareReturnTypes()
       : super(
             name: 'always_declare_return_types',
@@ -54,6 +57,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -66,14 +72,14 @@
 class _Visitor extends SimpleAstVisitor<void> {
   static const LintCode functionCode = LintCode(
       "always_declare_return_types", // ignore: prefer_single_quotes
-      "The function {0} should have a return type but doesn't.",
-      correction:
+      "The function '{0}' should have a return type but doesn't.",
+      correctionMessage:
           "Try adding a return type to the function."); // ignore: prefer_single_quotes
 
   static const LintCode methodCode = LintCode(
       "always_declare_return_types", // ignore: prefer_single_quotes
-      "The method {0} should have a return type but doesn't.",
-      correction:
+      "The method '{0}' should have a return type but doesn't.",
+      correctionMessage:
           "Try adding a return type to the method."); // ignore: prefer_single_quotes
 
   final LintRule rule;
@@ -83,16 +89,16 @@
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
     if (!node.isSetter && node.returnType == null) {
-      rule.reportLint(node.name,
-          arguments: [node.name.name], errorCode: functionCode);
+      rule.reportLintForToken(node.name,
+          arguments: [node.name.lexeme], errorCode: functionCode);
     }
   }
 
   @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
     if (node.returnType == null) {
-      rule.reportLint(node.name,
-          arguments: [node.name.name], errorCode: functionCode);
+      rule.reportLintForToken(node.name,
+          arguments: [node.name.lexeme], errorCode: functionCode);
     }
   }
 
@@ -100,9 +106,9 @@
   void visitMethodDeclaration(MethodDeclaration node) {
     if (!node.isSetter &&
         node.returnType == null &&
-        node.name.token.type != TokenType.INDEX_EQ) {
-      rule.reportLint(node.name,
-          arguments: [node.name.name], errorCode: methodCode);
+        node.name.type != TokenType.INDEX_EQ) {
+      rule.reportLintForToken(node.name,
+          arguments: [node.name.lexeme], errorCode: methodCode);
     }
   }
 }
diff --git a/lib/src/rules/always_put_control_body_on_new_line.dart b/lib/src/rules/always_put_control_body_on_new_line.dart
index f840450..8e9cc7f 100644
--- a/lib/src/rules/always_put_control_body_on_new_line.dart
+++ b/lib/src/rules/always_put_control_body_on_new_line.dart
@@ -10,8 +10,7 @@
 const _desc = r'Separate the control structure expression from its statement.';
 
 const _details = r'''
-
-From the [flutter style guide](https://flutter.dev/style-guide/):
+From the [style guide for the flutter repo](https://flutter.dev/style-guide/):
 
 **DO** separate the control structure expression from its statement.
 
@@ -19,6 +18,17 @@
 as the expression, even if it is short.  Doing so makes it unclear that there
 is relevant code there.  This is especially important for early returns.
 
+**BAD:**
+```dart
+if (notReady) return;
+
+if (notReady)
+  return;
+else print('ok')
+
+while (condition) i += 1;
+```
+
 **GOOD:**
 ```dart
 if (notReady)
@@ -33,20 +43,17 @@
   i += 1;
 ```
 
-**BAD:**
-```dart
-if (notReady) return;
-
-if (notReady)
-  return;
-else print('ok')
-
-while (condition) i += 1;
-```
+Note that this rule can conflict with the
+[Dart formatter](https://dart.dev/tools/dart-format), and should not be enabled
+when the Dart formatter is used.
 
 ''';
 
-class AlwaysPutControlBodyOnNewLine extends LintRule implements NodeLintRule {
+class AlwaysPutControlBodyOnNewLine extends LintRule {
+  static const LintCode code = LintCode('always_put_control_body_on_new_line',
+      'Statement should be on a separate line.',
+      correctionMessage: 'Try moving the statement to a new line.');
+
   AlwaysPutControlBodyOnNewLine()
       : super(
             name: 'always_put_control_body_on_new_line',
@@ -55,6 +62,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -102,9 +112,6 @@
     var offsetFirstStatement =
         node is Block ? node.statements.first.offset : node.offset;
     var lineInfo = unit.lineInfo;
-    if (lineInfo == null) {
-      return;
-    }
     if (lineInfo.getLocation(controlEnd).lineNumber ==
         lineInfo.getLocation(offsetFirstStatement).lineNumber) {
       rule.reportLintForToken(node.beginToken);
diff --git a/lib/src/rules/always_put_required_named_parameters_first.dart b/lib/src/rules/always_put_required_named_parameters_first.dart
index 79b2430..1df7aaa 100644
--- a/lib/src/rules/always_put_required_named_parameters_first.dart
+++ b/lib/src/rules/always_put_required_named_parameters_first.dart
@@ -10,14 +10,8 @@
 const _desc = r'Put required named parameters first.';
 
 const _details = r'''
-
 **DO** specify `required` on named parameter before other named parameters.
 
-**GOOD:**
-```dart
-m({required a, b, c}) ;
-```
-
 **BAD:**
 ```dart
 m({b, c, required a}) ;
@@ -25,7 +19,7 @@
 
 **GOOD:**
 ```dart
-m({@required a, b, c}) ;
+m({required a, b, c}) ;
 ```
 
 **BAD:**
@@ -33,10 +27,21 @@
 m({b, c, @required a}) ;
 ```
 
+**GOOD:**
+```dart
+m({@required a, b, c}) ;
+```
+
 ''';
 
-class AlwaysPutRequiredNamedParametersFirst extends LintRule
-    implements NodeLintRule {
+class AlwaysPutRequiredNamedParametersFirst extends LintRule {
+  static const LintCode code = LintCode(
+      'always_put_required_named_parameters_first',
+      'Required named parameters should be before optional named parameters.',
+      correctionMessage:
+          'Try moving the required named parameter to be before any optional '
+          'named parameters.');
+
   AlwaysPutRequiredNamedParametersFirst()
       : super(
             name: 'always_put_required_named_parameters_first',
@@ -45,6 +50,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -64,9 +72,9 @@
       var element = param.declaredElement;
       if (element != null && (element.hasRequired || element.isRequiredNamed)) {
         if (nonRequiredSeen) {
-          var identifier = param.identifier;
-          if (identifier != null) {
-            rule.reportLintForToken(identifier.token);
+          var name = param.name;
+          if (name != null) {
+            rule.reportLintForToken(name);
           }
         }
       } else {
diff --git a/lib/src/rules/always_require_non_null_named_parameters.dart b/lib/src/rules/always_require_non_null_named_parameters.dart
index c137fd5..4a3825c 100644
--- a/lib/src/rules/always_require_non_null_named_parameters.dart
+++ b/lib/src/rules/always_require_non_null_named_parameters.dart
@@ -8,15 +8,21 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Specify `@required` on named parameters without defaults.';
 
 const _details = r'''
-
 **DO** specify `@required` on named parameters without a default value on which 
 an `assert(param != null)` is done.
 
+**BAD:**
+```dart
+m1({a}) {
+  assert(a != null);
+}
+```
+
 **GOOD:**
 ```dart
 m1({@required a}) {
@@ -28,19 +34,16 @@
 }
 ```
 
-**BAD:**
-```dart
-m1({a}) {
-  assert(a != null);
-}
-```
-
 NOTE: Only asserts at the start of the bodies will be taken into account.
 
 ''';
 
-class AlwaysRequireNonNullNamedParameters extends LintRule
-    implements NodeLintRule {
+class AlwaysRequireNonNullNamedParameters extends LintRule {
+  static const LintCode code = LintCode(
+      'always_require_non_null_named_parameters',
+      "Named parameters without a default value should be annotated with '@required'.",
+      correctionMessage: "Try adding the '@required' annotation.");
+
   AlwaysRequireNonNullNamedParameters()
       : super(
             name: 'always_require_non_null_named_parameters',
@@ -49,6 +52,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     // In a Null Safety library, this lint is covered by other formal static
@@ -99,10 +105,9 @@
   void _checkAssert(
       Expression assertExpression, List<DefaultFormalParameter> params) {
     for (var param in params) {
-      var identifier = param.identifier;
-      if (identifier != null &&
-          _hasAssertNotNull(assertExpression, identifier.name)) {
-        rule.reportLintForToken(identifier.beginToken);
+      var name = param.name;
+      if (name != null && _hasAssertNotNull(assertExpression, name.lexeme)) {
+        rule.reportLintForToken(name);
         params.remove(param);
         return;
       }
@@ -118,7 +123,7 @@
     }
   }
 
-  void _checkParams(List<DefaultFormalParameter> params, FunctionBody? body) {
+  void _checkParams(List<DefaultFormalParameter> params, FunctionBody body) {
     if (body is BlockFunctionBody) {
       for (var statement in body.block.statements) {
         if (statement is AssertStatement) {
@@ -132,7 +137,7 @@
   }
 
   bool _hasAssertNotNull(Expression node, String name) {
-    bool _hasSameName(Expression rawExpression) {
+    bool hasSameName(Expression rawExpression) {
       var expression = rawExpression.unParenthesized;
       return expression is SimpleIdentifier && expression.name == name;
     }
@@ -145,8 +150,8 @@
       }
       if (expression.operator.type == TokenType.BANG_EQ) {
         var operands = [expression.leftOperand, expression.rightOperand];
-        return operands.any(DartTypeUtilities.isNullLiteral) &&
-            operands.any(_hasSameName);
+        return operands.any((e) => e.isNullLiteral) &&
+            operands.any(hasSameName);
       }
     }
     return false;
diff --git a/lib/src/rules/always_specify_types.dart b/lib/src/rules/always_specify_types.dart
index e3c7b3e..69ebd81 100644
--- a/lib/src/rules/always_specify_types.dart
+++ b/lib/src/rules/always_specify_types.dart
@@ -8,13 +8,12 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../utils.dart';
+import '../util/ascii_utils.dart';
 
 const _desc = r'Specify type annotations.';
 
 const _details = r'''
-
-From the [flutter style guide](https://flutter.dev/style-guide/):
+From the [style guide for the flutter repo](https://flutter.dev/style-guide/):
 
 **DO** specify type annotations.
 
@@ -23,6 +22,13 @@
 unknown.  Use `Object` if you are being explicit that you want an object that
 implements `==` and `hashCode`.
 
+**BAD:**
+```dart
+var foo = 10;
+final bar = Bar();
+const quux = 20;
+```
+
 **GOOD:**
 ```dart
 int foo = 10;
@@ -31,13 +37,6 @@
 const int quux = 20;
 ```
 
-**BAD:**
-```dart
-var foo = 10;
-final bar = Bar();
-const quux = 20;
-```
-
 NOTE: Using the the `@optionalTypeArgs` annotation in the `meta` package, API
 authors can special-case type variables whose type needs to by dynamic but whose
 declaration should be treated as optional.  For example, suppose you have a
@@ -59,24 +58,11 @@
 
 ''';
 
-/// The name of `meta` library, used to define analysis annotations.
-String _metaLibName = 'meta';
+class AlwaysSpecifyTypes extends LintRule {
+  static const LintCode code = LintCode(
+      'always_specify_types', 'Missing type annotation.',
+      correctionMessage: 'Try adding a type annotation.');
 
-/// The name of the top-level variable used to mark a Class as having optional
-/// type args.
-String _optionalTypeArgsVarName = 'optionalTypeArgs';
-
-bool _isOptionallyParameterized(TypeParameterizedElement element) {
-  var metadata = element.metadata;
-  return metadata.any((ElementAnnotation a) => _isOptionalTypeArgs(a.element));
-}
-
-bool _isOptionalTypeArgs(Element? element) =>
-    element is PropertyAccessorElement &&
-    element.name == _optionalTypeArgsVarName &&
-    element.library.name == _metaLibName;
-
-class AlwaysSpecifyTypes extends LintRule implements NodeLintRule {
   AlwaysSpecifyTypes()
       : super(
             name: 'always_specify_types',
@@ -89,6 +75,9 @@
       const ['avoid_types_on_closure_parameters', 'omit_local_variable_types'];
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -96,7 +85,7 @@
     registry.addListLiteral(this, visitor);
     registry.addSetOrMapLiteral(this, visitor);
     registry.addSimpleFormalParameter(this, visitor);
-    registry.addTypeName(this, visitor);
+    registry.addNamedType(this, visitor);
     registry.addVariableDeclarationList(this, visitor);
   }
 }
@@ -124,14 +113,16 @@
     checkLiteral(literal);
   }
 
+  @override
   void visitNamedType(NamedType namedType) {
     var type = namedType.type;
     if (type is InterfaceType) {
-      var element = type.aliasElement ?? type.element;
-      if (element.typeParameters.isNotEmpty &&
+      var element = namedType.name.staticElement;
+      if (element is TypeParameterizedElement &&
+          element.typeParameters.isNotEmpty &&
           namedType.typeArguments == null &&
           namedType.parent is! IsExpression &&
-          !_isOptionallyParameterized(element)) {
+          !element.hasOptionalTypeArgs) {
         rule.reportLint(namedType);
       }
     }
@@ -144,10 +135,8 @@
 
   @override
   void visitSimpleFormalParameter(SimpleFormalParameter param) {
-    var identifier = param.identifier;
-    if (identifier != null &&
-        param.type == null &&
-        !isJustUnderscores(identifier.name)) {
+    var name = param.name;
+    if (name != null && param.type == null && !name.lexeme.isJustUnderscores) {
       if (param.keyword != null) {
         rule.reportLintForToken(param.keyword);
       } else {
@@ -157,11 +146,6 @@
   }
 
   @override
-  void visitTypeName(NamedType typeName) {
-    visitNamedType(typeName);
-  }
-
-  @override
   void visitVariableDeclarationList(VariableDeclarationList list) {
     if (list.type == null) {
       rule.reportLintForToken(list.keyword);
diff --git a/lib/src/rules/always_use_package_imports.dart b/lib/src/rules/always_use_package_imports.dart
index fef98a5..336506f 100644
--- a/lib/src/rules/always_use_package_imports.dart
+++ b/lib/src/rules/always_use_package_imports.dart
@@ -10,30 +10,20 @@
 
 const _desc = r'Avoid relative imports for files in `lib/`.';
 
-const _details = r'''*DO* avoid relative imports for files in `lib/`.
+const _details = r'''
+**DO** avoid relative imports for files in `lib/`.
 
 When mixing relative and absolute imports it's possible to create confusion
 where the same member gets imported in two different ways. One way to avoid
-that is to ensure you consistently use absolute imports for files withing the
+that is to ensure you consistently use absolute imports for files within the
 `lib/` directory.
 
 This is the opposite of 'prefer_relative_imports'.
-Might be used with 'avoid_relative_lib_imports' to avoid relative imports of
-files within `lib/` directory outside of it. (for example `test/`)
 
-**GOOD:**
-
-```dart
-import 'package:foo/bar.dart';
-
-import 'package:foo/baz.dart';
-
-import 'package:foo/src/baz.dart';
-...
-```
+You can also use 'avoid_relative_lib_imports' to disallow relative imports of
+files within `lib/` directory outside of it (for example `test/`).
 
 **BAD:**
-
 ```dart
 import 'baz.dart';
 
@@ -44,9 +34,23 @@
 ...
 ```
 
+**GOOD:**
+```dart
+import 'package:foo/bar.dart';
+
+import 'package:foo/baz.dart';
+
+import 'package:foo/src/baz.dart';
+...
+```
+
 ''';
 
-class AlwaysUsePackageImports extends LintRule implements NodeLintRule {
+class AlwaysUsePackageImports extends LintRule {
+  static const LintCode code = LintCode('always_use_package_imports',
+      "Use 'package:' imports for files in the 'lib' directory.",
+      correctionMessage: "Try converting the URI to a 'package:' URI.");
+
   AlwaysUsePackageImports()
       : super(
             name: 'always_use_package_imports',
@@ -55,25 +59,29 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
+    // Relative paths from outside of the lib folder are handled by the
+    // `avoid_relative_lib_imports` lint.
     if (!isInLibDir(context.currentUnit.unit, context.package)) {
       return;
     }
 
-    var visitor = _Visitor(this, context);
+    var visitor = _Visitor(this);
     registry.addImportDirective(this, visitor);
   }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
-  final LinterContext context;
 
-  _Visitor(this.rule, this.context);
+  _Visitor(this.rule);
 
   bool isRelativeImport(ImportDirective node) {
-    var uriContent = node.uriContent;
+    var uriContent = node.uri.stringValue;
     if (uriContent != null) {
       var uri = Uri.tryParse(uriContent);
       return uri != null && uri.scheme.isEmpty;
diff --git a/lib/src/rules/annotate_overrides.dart b/lib/src/rules/annotate_overrides.dart
index a4c167b..5927226 100644
--- a/lib/src/rules/annotate_overrides.dart
+++ b/lib/src/rules/annotate_overrides.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 
@@ -11,12 +12,22 @@
 const _desc = r'Annotate overridden members.';
 
 const _details = r'''
-
 **DO** annotate overridden methods and fields.
 
 This practice improves code readability and helps protect against
 unintentionally overriding superclass members.
 
+**BAD:**
+```dart
+class Cat {
+  int get lives => 9;
+}
+
+class Lucky extends Cat {
+  final int lives = 14;
+}
+```
+
 **GOOD:**
 ```dart
 abstract class Dog {
@@ -32,20 +43,15 @@
 }
 ```
 
-**BAD:**
-```dart
-class Cat {
-  int get lives => 9;
-}
-
-class Lucky extends Cat {
-  final int lives = 14;
-}
-```
-
 ''';
 
-class AnnotateOverrides extends LintRule implements NodeLintRule {
+class AnnotateOverrides extends LintRule {
+  static const LintCode code = LintCode(
+      'annotate_overrides',
+      "The member '{0}' overrides an inherited member but isn't annotated "
+          "with '@override'.",
+      correctionMessage: "Try adding the '@override' annotation.");
+
   AnnotateOverrides()
       : super(
             name: 'annotate_overrides',
@@ -54,10 +60,12 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
-    registry.addCompilationUnit(this, visitor);
     registry.addFieldDeclaration(this, visitor);
     registry.addMethodDeclaration(this, visitor);
   }
@@ -69,8 +77,17 @@
 
   _Visitor(this.rule, this.context);
 
+  void check(Element? element, Token target) {
+    if (element == null || element.hasOverride) return;
+
+    var member = getOverriddenMember(element);
+    if (member != null) {
+      rule.reportLintForToken(target, arguments: [member.name!]);
+    }
+  }
+
   Element? getOverriddenMember(Element member) {
-    var classElement = member.thisOrAncestorOfType<ClassElement>();
+    var classElement = member.thisOrAncestorOfType<InterfaceElement>();
     if (classElement == null) {
       return null;
     }
@@ -88,25 +105,17 @@
 
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
+    if (node.isStatic) return;
+
     for (var field in node.fields.variables) {
-      var element = field.declaredElement;
-      if (element != null && !element.hasOverride) {
-        var member = getOverriddenMember(element);
-        if (member != null) {
-          rule.reportLint(field);
-        }
-      }
+      check(field.declaredElement, field.name);
     }
   }
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    var element = node.declaredElement;
-    if (element != null && !element.hasOverride) {
-      var member = getOverriddenMember(element);
-      if (member != null) {
-        rule.reportLint(node.name);
-      }
-    }
+    if (node.isStatic) return;
+
+    check(node.declaredElement, node.name);
   }
 }
diff --git a/lib/src/rules/avoid_annotating_with_dynamic.dart b/lib/src/rules/avoid_annotating_with_dynamic.dart
index d49632a..5227a8a 100644
--- a/lib/src/rules/avoid_annotating_with_dynamic.dart
+++ b/lib/src/rules/avoid_annotating_with_dynamic.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid annotating with dynamic when not required.';
 
 const _details = r'''
-
 **AVOID** annotating with dynamic when not required.
 
 As `dynamic` is the assumed return value of a function or method, it is usually
@@ -36,7 +35,11 @@
 
 ''';
 
-class AvoidAnnotatingWithDynamic extends LintRule implements NodeLintRule {
+class AvoidAnnotatingWithDynamic extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_annotating_with_dynamic', "Unnecessary 'dynamic' type annotation.",
+      correctionMessage: "Try removing the type 'dynamic'.");
+
   AvoidAnnotatingWithDynamic()
       : super(
             name: 'avoid_annotating_with_dynamic',
@@ -45,10 +48,15 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
+    registry.addFieldFormalParameter(this, visitor);
     registry.addSimpleFormalParameter(this, visitor);
+    registry.addSuperFormalParameter(this, visitor);
   }
 }
 
@@ -58,9 +66,22 @@
   _Visitor(this.rule);
 
   @override
+  void visitFieldFormalParameter(FieldFormalParameter node) {
+    _checkNode(node, node.type);
+  }
+
+  @override
   void visitSimpleFormalParameter(SimpleFormalParameter node) {
-    var type = node.type;
-    if (type is TypeName && type.name.name == 'dynamic') {
+    _checkNode(node, node.type);
+  }
+
+  @override
+  void visitSuperFormalParameter(SuperFormalParameter node) {
+    _checkNode(node, node.type);
+  }
+
+  void _checkNode(NormalFormalParameter node, TypeAnnotation? type) {
+    if (type is NamedType && type.name.name == 'dynamic') {
       rule.reportLint(node);
     }
   }
diff --git a/lib/src/rules/avoid_as.dart b/lib/src/rules/avoid_as.dart
index e8ed667..0a16dfa 100644
--- a/lib/src/rules/avoid_as.dart
+++ b/lib/src/rules/avoid_as.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid using `as`.';
 
 const _details = r'''
-
 **AVOID** using `as`.
 
 If you know the type is correct, use an assertion or assign to a more
@@ -48,11 +47,14 @@
 
 
 **DEPRECATED:** This advice is no longer recommended.
- 
+
 The rule will be removed in a future Linter release.
 ''';
 
-class AvoidAs extends LintRule implements NodeLintRule {
+class AvoidAs extends LintRule {
+  static const LintCode code = LintCode('avoid_as', "Unnecessary use of 'as'.",
+      correctionMessage: "Try adding an explicit type check ('is').");
+
   AvoidAs()
       : super(
           name: 'avoid_as',
@@ -63,6 +65,9 @@
         );
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/avoid_bool_literals_in_conditional_expressions.dart b/lib/src/rules/avoid_bool_literals_in_conditional_expressions.dart
index e38de83..58982a4 100644
--- a/lib/src/rules/avoid_bool_literals_in_conditional_expressions.dart
+++ b/lib/src/rules/avoid_bool_literals_in_conditional_expressions.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid bool literals in conditional expressions.';
 
 const _details = r'''
-
 **AVOID** bool literals in conditional expressions.
 
 **BAD:**
@@ -31,8 +30,13 @@
 
 ''';
 
-class AvoidBoolLiteralsInConditionalExpressions extends LintRule
-    implements NodeLintRule {
+class AvoidBoolLiteralsInConditionalExpressions extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_bool_literals_in_conditional_expressions',
+      "Conditional expressions with a 'bool' literal can be simplified.",
+      correctionMessage:
+          "Try rewriting the expression to use either '&&' or '||'.");
+
   AvoidBoolLiteralsInConditionalExpressions()
       : super(
             name: 'avoid_bool_literals_in_conditional_expressions',
@@ -41,6 +45,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
diff --git a/lib/src/rules/avoid_catches_without_on_clauses.dart b/lib/src/rules/avoid_catches_without_on_clauses.dart
index d69b348..3179799 100644
--- a/lib/src/rules/avoid_catches_without_on_clauses.dart
+++ b/lib/src/rules/avoid_catches_without_on_clauses.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid catches without on clauses.';
 
 const _details = r'''
-
 **AVOID** catches without on clauses.
 
 Using catch clauses without on clauses make your code prone to encountering
@@ -38,7 +37,13 @@
 
 ''';
 
-class AvoidCatchesWithoutOnClauses extends LintRule implements NodeLintRule {
+class AvoidCatchesWithoutOnClauses extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_catches_without_on_clauses',
+      "Catch clause needs to use 'on' to specify the type of exception being "
+          'caught.',
+      correctionMessage: "Try adding an 'on' clause before the 'catch'.");
+
   AvoidCatchesWithoutOnClauses()
       : super(
             name: 'avoid_catches_without_on_clauses',
@@ -47,6 +52,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/avoid_catching_errors.dart b/lib/src/rules/avoid_catching_errors.dart
index 39d243d..44c01b8 100644
--- a/lib/src/rules/avoid_catching_errors.dart
+++ b/lib/src/rules/avoid_catching_errors.dart
@@ -6,12 +6,11 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r"Don't explicitly catch Error or types that implement it.";
 
 const _details = r'''
-
 **DON'T** explicitly catch Error or types that implement it.
 
 Errors differ from Exceptions in that Errors can be analyzed and prevented prior
@@ -37,7 +36,15 @@
 
 ''';
 
-class AvoidCatchingErrors extends LintRule implements NodeLintRule {
+class AvoidCatchingErrors extends LintRule {
+  static const LintCode classCode = LintCode(
+      'avoid_catching_errors', "The type 'Error' should not be caught.",
+      uniqueName: 'LintCode.avoid_catching_errors_class');
+
+  static const LintCode subclassCode = LintCode('avoid_catching_errors',
+      "The type '{0}' should not be caught because it is a subclass of 'Error'.",
+      uniqueName: 'LintCode.avoid_catching_errors_subclass');
+
   AvoidCatchingErrors()
       : super(
             name: 'avoid_catching_errors',
@@ -46,6 +53,9 @@
             group: Group.style);
 
   @override
+  List<LintCode> get lintCodes => [classCode, subclassCode];
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -61,9 +71,16 @@
   @override
   void visitCatchClause(CatchClause node) {
     var exceptionType = node.exceptionType?.type;
-    if (DartTypeUtilities.implementsInterface(
-        exceptionType, 'Error', 'dart.core')) {
-      rule.reportLint(node);
+    if (exceptionType.implementsInterface('Error', 'dart.core')) {
+      if (exceptionType.isSameAs('Error', 'dart.core')) {
+        rule.reportLint(node, errorCode: AvoidCatchingErrors.classCode);
+      } else {
+        rule.reportLint(node,
+            errorCode: AvoidCatchingErrors.subclassCode,
+            arguments: [
+              exceptionType!.getDisplayString(withNullability: false)
+            ]);
+      }
     }
   }
 }
diff --git a/lib/src/rules/avoid_classes_with_only_static_members.dart b/lib/src/rules/avoid_classes_with_only_static_members.dart
index da90d8d..438fba9 100644
--- a/lib/src/rules/avoid_classes_with_only_static_members.dart
+++ b/lib/src/rules/avoid_classes_with_only_static_members.dart
@@ -11,6 +11,7 @@
 const _desc = r'Avoid defining a class that contains only static members.';
 
 const _details = r'''
+From [Effective Dart](https://dart.dev/guides/language/effective-dart/design#avoid-defining-a-class-that-contains-only-static-members):
 
 **AVOID** defining a class that contains only static members.
 
@@ -54,8 +55,13 @@
   return false;
 }
 
-class AvoidClassesWithOnlyStaticMembers extends LintRule
-    implements NodeLintRule {
+class AvoidClassesWithOnlyStaticMembers extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_classes_with_only_static_members',
+      'Classes should define instance members.',
+      correctionMessage:
+          'Try adding instance behavior or moving the members out of the class.');
+
   AvoidClassesWithOnlyStaticMembers()
       : super(
             name: 'avoid_classes_with_only_static_members',
@@ -64,6 +70,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
diff --git a/lib/src/rules/avoid_double_and_int_checks.dart b/lib/src/rules/avoid_double_and_int_checks.dart
index d509f1d..3401f8b 100644
--- a/lib/src/rules/avoid_double_and_int_checks.dart
+++ b/lib/src/rules/avoid_double_and_int_checks.dart
@@ -11,7 +11,6 @@
 const _desc = r'Avoid double and int checks.';
 
 const _details = r'''
-
 **AVOID** to check if type is double or int.
 
 When compiled to JS, integer values are represented as floats. That can lead to
@@ -42,7 +41,7 @@
 
 ''';
 
-class AvoidDoubleAndIntChecks extends LintRule implements NodeLintRule {
+class AvoidDoubleAndIntChecks extends LintRule {
   AvoidDoubleAndIntChecks()
       : super(
             name: 'avoid_double_and_int_checks',
diff --git a/lib/src/rules/avoid_dynamic_calls.dart b/lib/src/rules/avoid_dynamic_calls.dart
index 7ca696f..b90678a 100644
--- a/lib/src/rules/avoid_dynamic_calls.dart
+++ b/lib/src/rules/avoid_dynamic_calls.dart
@@ -12,7 +12,6 @@
 const _desc = r'Avoid method calls or property accesses on a "dynamic" target.';
 
 const _details = r'''
-
 **DO** avoid method calls or accessing properties on an object that is either
 explicitly or implicitly statically typed "dynamic". Dynamic calls are treated
 slightly different in every runtime environment and compiler, but most
@@ -91,17 +90,24 @@
 
 ''';
 
-class AvoidDynamicCalls extends LintRule implements NodeLintRule {
+class AvoidDynamicCalls extends LintRule {
+  static const LintCode code = LintCode('avoid_dynamic_calls',
+      "Method invocation or property access on a 'dynamic' target.",
+      correctionMessage: 'Try giving the target a type.');
+
   AvoidDynamicCalls()
       : super(
           name: 'avoid_dynamic_calls',
           description: _desc,
           details: _details,
           group: Group.errors,
-          maturity: Maturity.experimental,
+          maturity: Maturity.stable,
         );
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
     NodeLintRegistry registry,
     LinterContext context,
@@ -125,28 +131,6 @@
 
   _Visitor(this.rule);
 
-  bool _lintIfDynamic(Expression? node) {
-    if (node?.staticType?.isDynamic ?? false) {
-      rule.reportLint(node);
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  void _lintIfDynamicOrFunction(Expression node, {DartType? staticType}) {
-    staticType ??= node.staticType;
-    if (staticType == null) {
-      return;
-    }
-    if (staticType.isDynamic) {
-      rule.reportLint(node);
-    }
-    if (staticType.isDartCoreFunction) {
-      rule.reportLint(node);
-    }
-  }
-
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
     if (node.readType?.isDynamic != true) {
@@ -226,30 +210,12 @@
     }
   }
 
-  void _lintPrefixOrPostfixExpression(Expression root, Expression operand) {
-    if (_lintIfDynamic(operand)) {
+  @override
+  void visitPostfixExpression(PostfixExpression node) {
+    if (node.operator.type == TokenType.BANG) {
+      // x! is not a dynamic call, even if "x" is dynamic.
       return;
     }
-    if (root is CompoundAssignmentExpression) {
-      // Not guaranteed to be promoted by "is" since in older analyzers,
-      // CompoundAssignmentExpression didn't implement Expression (so the
-      // promotion would lose capabilities).  TODO(paulberry): in the future,
-      // when we depend on a version of the analyzer in which
-      // CompoundAssignmentExpression is guaranteed to implement Expression,
-      // remove the cast.
-      var rootAsAssignment =
-          root as CompoundAssignmentExpression; // ignore: unnecessary_cast
-      if (rootAsAssignment.readType?.isDynamic ?? false) {
-        // An assignment expression can only be a dynamic call if it is a
-        // "compound assignment" (i.e. such as `x += 1`); so if `readType` is
-        // dynamic we should lint.
-        rule.reportLint(root);
-      }
-    }
-  }
-
-  @override
-  void visitPrefixExpression(PrefixExpression node) {
     _lintPrefixOrPostfixExpression(node, node.operand);
   }
 
@@ -269,11 +235,7 @@
   }
 
   @override
-  void visitPostfixExpression(PostfixExpression node) {
-    if (node.operator.type == TokenType.BANG) {
-      // x! is not a dynamic call, even if "x" is dynamic.
-      return;
-    }
+  void visitPrefixExpression(PrefixExpression node) {
     _lintPrefixOrPostfixExpression(node, node.operand);
   }
 
@@ -281,4 +243,40 @@
   void visitPropertyAccess(PropertyAccess node) {
     _lintIfDynamic(node.realTarget);
   }
+
+  bool _lintIfDynamic(Expression? node) {
+    if (node?.staticType?.isDynamic ?? false) {
+      rule.reportLint(node);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  void _lintIfDynamicOrFunction(Expression node, {DartType? staticType}) {
+    staticType ??= node.staticType;
+    if (staticType == null) {
+      return;
+    }
+    if (staticType.isDynamic) {
+      rule.reportLint(node);
+    }
+    if (staticType.isDartCoreFunction) {
+      rule.reportLint(node);
+    }
+  }
+
+  void _lintPrefixOrPostfixExpression(Expression root, Expression operand) {
+    if (_lintIfDynamic(operand)) {
+      return;
+    }
+    if (root is CompoundAssignmentExpression) {
+      if (root.readType?.isDynamic ?? false) {
+        // An assignment expression can only be a dynamic call if it is a
+        // "compound assignment" (i.e. such as `x += 1`); so if `readType` is
+        // dynamic we should lint.
+        rule.reportLint(root);
+      }
+    }
+  }
 }
diff --git a/lib/src/rules/avoid_empty_else.dart b/lib/src/rules/avoid_empty_else.dart
index dc22923..16ea88c 100644
--- a/lib/src/rules/avoid_empty_else.dart
+++ b/lib/src/rules/avoid_empty_else.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid empty else statements.';
 
 const _details = r'''
-
 **AVOID** empty else statements.
 
 **BAD:**
@@ -23,7 +22,15 @@
 
 ''';
 
-class AvoidEmptyElse extends LintRule implements NodeLintRule {
+class AvoidEmptyElse extends LintRule {
+  static const LintCode code = LintCode('avoid_empty_else',
+      "Empty statements are not allowed in an 'else' clause.",
+      correctionMessage:
+          'Try removing the empty statement or removing the else clause.');
+
+  @override
+  LintCode get lintCode => code;
+
   AvoidEmptyElse()
       : super(
             name: 'avoid_empty_else',
diff --git a/lib/src/rules/avoid_equals_and_hash_code_on_mutable_classes.dart b/lib/src/rules/avoid_equals_and_hash_code_on_mutable_classes.dart
index 7c28d72..828a88e 100644
--- a/lib/src/rules/avoid_equals_and_hash_code_on_mutable_classes.dart
+++ b/lib/src/rules/avoid_equals_and_hash_code_on_mutable_classes.dart
@@ -14,7 +14,6 @@
     r'Avoid overloading operator == and hashCode on classes not marked `@immutable`.';
 
 const _details = r'''
-
 **AVOID** overloading operator == and hashCode on classes not marked `@immutable`.
 
 If a class is not immutable, overloading operator == and hashCode can lead to
@@ -22,6 +21,18 @@
 https://dart.dev/guides/language/effective-dart/design#avoid-defining-custom-equality-for-mutable-classes
 for more information.
 
+**BAD:**
+```dart
+class B {
+  String key;
+  const B(this.key);
+  @override
+  operator ==(other) => other is B && other.key == key;
+  @override
+  int get hashCode => key.hashCode;
+}
+```
+
 **GOOD:**
 ```dart
 @immutable
@@ -31,23 +42,11 @@
   @override
   operator ==(other) => other is A && other.key == key;
   @override
-  int hashCode() => key.hashCode;
+  int get hashCode => key.hashCode;
 }
 ```
 
-**BAD:**
-```dart
-class B {
-  String key;
-  const B(this.key);
-  @override
-  operator ==(other) => other is B && other.key == key;
-  @override
-  int hashCode() => key.hashCode;
-}
-```
-
-NOTE: The lint checks the use of the @immutable annotation, and will trigger
+NOTE: The lint checks the use of the `@immutable` annotation, and will trigger
 even if the class is otherwise not mutable. Thus:
 
 **BAD:**
@@ -56,9 +55,9 @@
   final String key;
   const C(this.key);
   @override
-  operator ==(other) => other is B && other.key == key;
+  operator ==(other) => other is C && other.key == key;
   @override
-  int hashCode() => key.hashCode;
+  int get hashCode => key.hashCode;
 }
 ```
 
@@ -75,8 +74,13 @@
     element.name == _immutableVarName &&
     element.library.name == _metaLibName;
 
-class AvoidOperatorEqualsOnMutableClasses extends LintRule
-    implements NodeLintRule {
+class AvoidOperatorEqualsOnMutableClasses extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_equals_and_hash_code_on_mutable_classes',
+      "The method '{0}' should not be overriden in classes not annotated with '@immutable'.",
+      correctionMessage:
+          "Try removing the override or annotating the class with '@immutable'.");
+
   AvoidOperatorEqualsOnMutableClasses()
       : super(
             name: 'avoid_equals_and_hash_code_on_mutable_classes',
@@ -85,9 +89,12 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this, context);
+    var visitor = _Visitor(this);
     registry.addMethodDeclaration(this, visitor);
   }
 }
@@ -95,16 +102,15 @@
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
-  final LinterContext context;
-
-  _Visitor(this.rule, this.context);
+  _Visitor(this.rule);
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    if (node.name.token.type == TokenType.EQ_EQ || isHashCode(node)) {
+    if (node.name.type == TokenType.EQ_EQ || isHashCode(node)) {
       var classElement = _getClassForMethod(node);
       if (classElement != null && !_hasImmutableAnnotation(classElement)) {
-        rule.reportLintForToken(node.firstTokenAfterCommentAndMetadata);
+        rule.reportLintForToken(node.firstTokenAfterCommentAndMetadata,
+            arguments: [node.name.lexeme]);
       }
     }
   }
@@ -114,7 +120,7 @@
       node.thisOrAncestorOfType<ClassDeclaration>()?.declaredElement;
 
   bool _hasImmutableAnnotation(ClassElement clazz) {
-    var inheritedAndSelfElements = <ClassElement>[
+    var inheritedAndSelfElements = <InterfaceElement>[
       ...clazz.allSupertypes.map((t) => t.element),
       clazz,
     ];
diff --git a/lib/src/rules/avoid_escaping_inner_quotes.dart b/lib/src/rules/avoid_escaping_inner_quotes.dart
index 19375e1..07116f7 100644
--- a/lib/src/rules/avoid_escaping_inner_quotes.dart
+++ b/lib/src/rules/avoid_escaping_inner_quotes.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid escaping inner quotes by converting surrounding quotes.';
 
 const _details = r'''
-
 Avoid escaping inner quotes by converting surrounding quotes.
 
 **BAD:**
@@ -25,7 +24,11 @@
 
 ''';
 
-class AvoidEscapingInnerQuotes extends LintRule implements NodeLintRule {
+class AvoidEscapingInnerQuotes extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_escaping_inner_quotes', "Unnecessary escape of '{0}'.",
+      correctionMessage: "Try changing the outer quotes to '{1}'.");
+
   AvoidEscapingInnerQuotes()
       : super(
             name: 'avoid_escaping_inner_quotes',
@@ -34,6 +37,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -50,26 +56,30 @@
   @override
   void visitSimpleStringLiteral(SimpleStringLiteral node) {
     if (node.isRaw || node.isMultiline) return;
-
-    if (isChangeable(node.value, isSingleQuoted: node.isSingleQuoted)) {
-      rule.reportLint(node);
-    }
+    _check(node, node.value, node.isSingleQuoted);
   }
 
   @override
   void visitStringInterpolation(StringInterpolation node) {
     if (node.isRaw || node.isMultiline) return;
 
-    var text = node.elements
-        .whereType<InterpolationString>()
-        .map((e) => e.value)
-        .reduce((a, b) => '$a$b');
-    if (isChangeable(text, isSingleQuoted: node.isSingleQuoted)) {
-      rule.reportLint(node);
+    var text = StringBuffer();
+    for (var element in node.elements) {
+      if (element is InterpolationString) {
+        text.write(element.value);
+      }
+    }
+    _check(node, text.toString(), node.isSingleQuoted);
+  }
+
+  void _check(AstNode node, String text, bool isSingleQuoted) {
+    if (_isChangeable(text, isSingleQuoted: isSingleQuoted)) {
+      rule.reportLint(node,
+          arguments: [isSingleQuoted ? "'" : '"', isSingleQuoted ? '"' : "'"]);
     }
   }
 
-  bool isChangeable(String text, {required bool isSingleQuoted}) =>
+  bool _isChangeable(String text, {required bool isSingleQuoted}) =>
       text.contains(isSingleQuoted ? "'" : '"') &&
       !text.contains(isSingleQuoted ? '"' : "'");
 }
diff --git a/lib/src/rules/avoid_field_initializers_in_const_classes.dart b/lib/src/rules/avoid_field_initializers_in_const_classes.dart
index 2518507..9739579 100644
--- a/lib/src/rules/avoid_field_initializers_in_const_classes.dart
+++ b/lib/src/rules/avoid_field_initializers_in_const_classes.dart
@@ -11,7 +11,6 @@
 const _desc = r'Avoid field initializers in const classes.';
 
 const _details = r'''
-
 **AVOID** field initializers in const classes.
 
 Instead of `final x = const expr;`, you should write `get x => const expr;` and
@@ -36,8 +35,14 @@
 
 ''';
 
-class AvoidFieldInitializersInConstClasses extends LintRule
-    implements NodeLintRule {
+class AvoidFieldInitializersInConstClasses extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_field_initializers_in_const_classes',
+      "Fields in 'const' classes should not have initializers.",
+      correctionMessage:
+          'Try converting the field to a getter or initialize the field in the '
+          'constructors.');
+
   AvoidFieldInitializersInConstClasses()
       : super(
             name: 'avoid_field_initializers_in_const_classes',
@@ -46,6 +51,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/avoid_final_parameters.dart b/lib/src/rules/avoid_final_parameters.dart
new file mode 100644
index 0000000..606ed4a
--- /dev/null
+++ b/lib/src/rules/avoid_final_parameters.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Avoid final for parameter declarations.';
+
+const _details = r'''
+**AVOID** declaring parameters as final.
+
+Declaring parameters as final can lead to unnecessarily verbose code, especially
+when using the "parameter_assignments" rule.
+
+**BAD:**
+```dart
+void goodParameter(final String label) { // LINT
+  print(label);
+}
+```
+
+**GOOD:**
+```dart
+void badParameter(String label) { // OK
+  print(label);
+}
+```
+
+**BAD:**
+```dart
+void goodExpression(final int value) => print(value); // LINT
+```
+
+**GOOD:**
+```dart
+void badExpression(int value) => print(value); // OK
+```
+
+**BAD:**
+```dart
+[1, 4, 6, 8].forEach((final value) => print(value + 2)); // LINT
+```
+
+**GOOD:**
+```dart
+[1, 4, 6, 8].forEach((value) => print(value + 2)); // OK
+```
+
+''';
+
+class AvoidFinalParameters extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_final_parameters', "Parameters should not be marked as 'final'.",
+      correctionMessage: "Try removing the keyword 'final'.");
+
+  AvoidFinalParameters()
+      : super(
+            name: 'avoid_final_parameters',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  List<String> get incompatibleRules => const ['prefer_final_parameters'];
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addConstructorDeclaration(this, visitor);
+    registry.addFunctionExpression(this, visitor);
+    registry.addMethodDeclaration(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitConstructorDeclaration(ConstructorDeclaration node) =>
+      _reportApplicableParameters(node.parameters);
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) =>
+      _reportApplicableParameters(node.parameters);
+
+  @override
+  void visitMethodDeclaration(MethodDeclaration node) =>
+      _reportApplicableParameters(node.parameters);
+
+  /// Report the lint for parameters in the [parameters] list that are final.
+  void _reportApplicableParameters(FormalParameterList? parameters) {
+    if (parameters != null) {
+      for (var param in parameters.parameters) {
+        if (param.isFinal) {
+          rule.reportLint(param);
+        }
+      }
+    }
+  }
+}
diff --git a/lib/src/rules/avoid_function_literals_in_foreach_calls.dart b/lib/src/rules/avoid_function_literals_in_foreach_calls.dart
index 69e1620..037f383 100644
--- a/lib/src/rules/avoid_function_literals_in_foreach_calls.dart
+++ b/lib/src/rules/avoid_function_literals_in_foreach_calls.dart
@@ -8,14 +8,19 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Avoid using `forEach` with a function literal.';
 
 const _details = r'''
-
 **AVOID** using `forEach` with a function literal.
 
+The `for` loop enables a developer to be clear and explicit as to their intent.
+A return in the body of the `for` loop returns from the body of the function,
+where as a return in the body of the `forEach` closure only returns a value
+for that iteration of the `forEach`. The body of a `for` loop can contain
+`await`s, while the closure body of a `forEach` cannot.
+
 **BAD:**
 ```dart
 people.forEach((person) {
@@ -28,8 +33,6 @@
 for (var person in people) {
   ...
 }
-
-people.forEach(print);
 ```
 ''';
 
@@ -52,10 +55,14 @@
 bool _isNonNullableIterable(DartType? type) =>
     type != null &&
     type.nullabilitySuffix != NullabilitySuffix.question &&
-    DartTypeUtilities.implementsInterface(type, 'Iterable', 'dart.core');
+    type.implementsInterface('Iterable', 'dart.core');
 
-class AvoidFunctionLiteralInForeachMethod extends LintRule
-    implements NodeLintRule {
+class AvoidFunctionLiteralInForeachMethod extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_function_literals_in_foreach_calls',
+      "Function literals shouldn't be passed to 'forEach'.",
+      correctionMessage: "Try using a 'for' loop.");
+
   AvoidFunctionLiteralInForeachMethod()
       : super(
             name: 'avoid_function_literals_in_foreach_calls',
@@ -64,6 +71,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -82,7 +92,7 @@
     if (target != null &&
         node.methodName.token.value() == 'forEach' &&
         node.argumentList.arguments.isNotEmpty &&
-        node.argumentList.arguments[0] is FunctionExpression &&
+        node.argumentList.arguments.first is FunctionExpression &&
         _isNonNullableIterable(target.staticType) &&
         !_hasMethodChaining(node)) {
       rule.reportLint(node.function);
diff --git a/lib/src/rules/avoid_implementing_value_types.dart b/lib/src/rules/avoid_implementing_value_types.dart
index 515d7d6..cdaf29d 100644
--- a/lib/src/rules/avoid_implementing_value_types.dart
+++ b/lib/src/rules/avoid_implementing_value_types.dart
@@ -5,12 +5,14 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
 
 const _desc = r"Don't implement classes that override `==`.";
 
-const _details = r'''**DON'T** implement classes that override `==`.
+const _details = r'''
+**DON'T** implement classes that override `==`.
 
 The `==` operator is contractually required to be an equivalence relation;
 that is, symmetrically for all objects `o1` and `o2`, `o1 == o2` and `o2 == o1`
@@ -37,7 +39,7 @@
 }
 ```
 
-**BAD**:
+**BAD:**
 ```dart
 class CustomSize implements Size {
   final int inBytes;
@@ -47,7 +49,7 @@
 }
 ```
 
-**BAD**:
+**BAD:**
 ```dart
 import 'package:test/test.dart';
 import 'size.dart';
@@ -63,7 +65,7 @@
 }
 ```
 
-**GOOD**:
+**GOOD:**
 ```dart
 class ExtendedSize extends Size {
   ExtendedSize(int inBytes) : super(inBytes);
@@ -72,7 +74,7 @@
 }
 ```
 
-**GOOD**:
+**GOOD:**:
 ```dart
 import 'package:test/test.dart';
 import 'size.dart';
@@ -86,7 +88,12 @@
 
 ''';
 
-class AvoidImplementingValueTypes extends LintRule implements NodeLintRule {
+class AvoidImplementingValueTypes extends LintRule {
+  static const LintCode code = LintCode('avoid_implementing_value_types',
+      "Classes that override '==' should not be implemented.",
+      correctionMessage:
+          "Try removing the class from the 'implements' clause.");
+
   AvoidImplementingValueTypes()
       : super(
             name: 'avoid_implementing_value_types',
@@ -95,6 +102,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -114,14 +124,15 @@
       return;
     }
     for (var interface in implementsClause.interfaces) {
-      var element = interface.type?.element;
-      if (element is ClassElement && _overridesEquals(element)) {
+      var interfaceType = interface.type;
+      if (interfaceType is InterfaceType &&
+          _overridesEquals(interfaceType.element)) {
         rule.reportLint(interface);
       }
     }
   }
 
-  static bool _overridesEquals(ClassElement element) {
+  static bool _overridesEquals(InterfaceElement element) {
     var method = element.lookUpConcreteMethod('==', element.library);
     var enclosing = method?.enclosingElement;
     return enclosing is ClassElement && !enclosing.isDartCoreObject;
diff --git a/lib/src/rules/avoid_init_to_null.dart b/lib/src/rules/avoid_init_to_null.dart
index c46f758..bccc532 100644
--- a/lib/src/rules/avoid_init_to_null.dart
+++ b/lib/src/rules/avoid_init_to_null.dart
@@ -5,16 +5,16 @@
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r"Don't explicitly initialize variables to null.";
 
 const _details = r'''
-
-From [effective dart](https://dart.dev/guides/language/effective-dart/usage#dont-explicitly-initialize-variables-to-null):
+From [Effective Dart](https://dart.dev/guides/language/effective-dart/usage#dont-explicitly-initialize-variables-to-null):
 
 **DON'T** explicitly initialize variables to null.
 
@@ -23,22 +23,6 @@
 no concept of "uninitialized memory" in Dart.  Adding `= null` is redundant and
 unneeded.
 
-**GOOD:**
-```dart
-int _nextId;
-
-class LazyId {
-  int _id;
-
-  int get id {
-    if (_nextId == null) _nextId = 0;
-    if (_id == null) _id = _nextId++;
-
-    return _id;
-  }
-}
-```
-
 **BAD:**
 ```dart
 int _nextId = null;
@@ -55,9 +39,29 @@
 }
 ```
 
+**GOOD:**
+```dart
+int _nextId;
+
+class LazyId {
+  int _id;
+
+  int get id {
+    if (_nextId == null) _nextId = 0;
+    if (_id == null) _id = _nextId++;
+
+    return _id;
+  }
+}
+```
+
 ''';
 
-class AvoidInitToNull extends LintRule implements NodeLintRule {
+class AvoidInitToNull extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_init_to_null', "Redundant initialization to 'null'.",
+      correctionMessage: 'Try removing the initializer.');
+
   AvoidInitToNull()
       : super(
             name: 'avoid_init_to_null',
@@ -66,6 +70,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -83,16 +90,21 @@
       : nnbdEnabled = context.isEnabled(Feature.non_nullable);
 
   bool isNullable(DartType type) =>
-      !nnbdEnabled || (context.typeSystem.isNullable(type));
+      !nnbdEnabled || context.typeSystem.isNullable(type);
 
   @override
   void visitDefaultFormalParameter(DefaultFormalParameter node) {
     var declaredElement = node.declaredElement;
-    if (declaredElement == null) {
-      return;
+    if (declaredElement == null) return;
+
+    if (declaredElement is SuperFormalParameterElement) {
+      var superConstructorParameter = declaredElement.superConstructorParameter;
+      if (superConstructorParameter is! ParameterElement) return;
+      var defaultValue = superConstructorParameter.defaultValueCode ?? 'null';
+      if (defaultValue != 'null') return;
     }
-    if (DartTypeUtilities.isNullLiteral(node.defaultValue) &&
-        isNullable(declaredElement.type)) {
+
+    if (node.defaultValue.isNullLiteral && isNullable(declaredElement.type)) {
       rule.reportLint(node);
     }
   }
@@ -103,7 +115,7 @@
     if (declaredElement != null &&
         !node.isConst &&
         !node.isFinal &&
-        DartTypeUtilities.isNullLiteral(node.initializer) &&
+        node.initializer.isNullLiteral &&
         isNullable(declaredElement.type)) {
       rule.reportLint(node);
     }
diff --git a/lib/src/rules/avoid_js_rounded_ints.dart b/lib/src/rules/avoid_js_rounded_ints.dart
index 02f6cd1..6627c95 100644
--- a/lib/src/rules/avoid_js_rounded_ints.dart
+++ b/lib/src/rules/avoid_js_rounded_ints.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid JavaScript rounded ints.';
 
 const _details = r'''
-
 **AVOID** integer literals that cannot be represented exactly when compiled to
 JavaScript.
 
@@ -33,7 +32,13 @@
 
 ''';
 
-class AvoidJsRoundedInts extends LintRule implements NodeLintRule {
+class AvoidJsRoundedInts extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_js_rounded_ints',
+      "Integer literal can't be represented exactly when compiled to "
+          'JavaScript.',
+      correctionMessage: "Try using a 'BigInt' to represent the value.");
+
   AvoidJsRoundedInts()
       : super(
             name: 'avoid_js_rounded_ints',
@@ -42,6 +47,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/avoid_multiple_declarations_per_line.dart b/lib/src/rules/avoid_multiple_declarations_per_line.dart
index ad057aa..5cdf90e 100644
--- a/lib/src/rules/avoid_multiple_declarations_per_line.dart
+++ b/lib/src/rules/avoid_multiple_declarations_per_line.dart
@@ -10,7 +10,6 @@
 const _desc = r"Don't declare multiple variables on a single line.";
 
 const _details = r'''
-
 **DON'T** declare multiple variables on a single line.
 
 **BAD:**
@@ -27,8 +26,12 @@
 
 ''';
 
-class AvoidMultipleDeclarationsPerLine extends LintRule
-    implements NodeLintRule {
+class AvoidMultipleDeclarationsPerLine extends LintRule {
+  static const LintCode code = LintCode('avoid_multiple_declarations_per_line',
+      'Multiple variables declared on a single line.',
+      correctionMessage:
+          'Try splitting the variable declarations into multiple lines.');
+
   AvoidMultipleDeclarationsPerLine()
       : super(
             name: 'avoid_multiple_declarations_per_line',
@@ -37,6 +40,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -51,11 +57,13 @@
 
   @override
   void visitVariableDeclarationList(VariableDeclarationList node) {
-    var variables = node.variables;
+    var parent = node.parent;
+    if (parent is ForPartsWithDeclarations && parent.variables == node) return;
 
+    var variables = node.variables;
     if (variables.length > 1) {
       var secondVariable = variables[1];
-      rule.reportLint(secondVariable.name);
+      rule.reportLintForToken(secondVariable.name);
     }
   }
 }
diff --git a/lib/src/rules/avoid_null_checks_in_equality_operators.dart b/lib/src/rules/avoid_null_checks_in_equality_operators.dart
index b604996..8facf47 100644
--- a/lib/src/rules/avoid_null_checks_in_equality_operators.dart
+++ b/lib/src/rules/avoid_null_checks_in_equality_operators.dart
@@ -2,18 +2,19 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r"Don't check for null in custom == operators.";
 
 const _details = r'''
-
 **DON'T** check for null in custom == operators.
 
 As null is a special type, no class can be equivalent to it.  Thus, it is
@@ -47,32 +48,25 @@
 
 bool _isComparingParameterWithNull(BinaryExpression node, Element? parameter) =>
     _isComparingEquality(node.operator.type) &&
-    ((DartTypeUtilities.isNullLiteral(node.leftOperand) &&
+    ((node.leftOperand.isNullLiteral &&
             _isParameter(node.rightOperand, parameter)) ||
-        (DartTypeUtilities.isNullLiteral(node.rightOperand) &&
+        (node.rightOperand.isNullLiteral &&
             _isParameter(node.leftOperand, parameter)));
 
 bool _isParameter(Expression expression, Element? parameter) =>
-    DartTypeUtilities.getCanonicalElementFromIdentifier(expression) ==
-    parameter;
-
-bool _isParameterWithQuestion(AstNode node, Element? parameter) =>
-    (node is PropertyAccess &&
-        node.operator.type == TokenType.QUESTION_PERIOD &&
-        DartTypeUtilities.getCanonicalElementFromIdentifier(node.target) ==
-            parameter) ||
-    (node is MethodInvocation &&
-        node.operator?.type == TokenType.QUESTION_PERIOD &&
-        DartTypeUtilities.getCanonicalElementFromIdentifier(node.target) ==
-            parameter);
+    expression.canonicalElement == parameter;
 
 bool _isParameterWithQuestionQuestion(
         BinaryExpression node, Element? parameter) =>
     node.operator.type == TokenType.QUESTION_QUESTION &&
     _isParameter(node.leftOperand, parameter);
 
-class AvoidNullChecksInEqualityOperators extends LintRule
-    implements NodeLintRule {
+class AvoidNullChecksInEqualityOperators extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_null_checks_in_equality_operators',
+      "Unnecessary null comparison in implementation of '=='.",
+      correctionMessage: 'Try removing the comparison.');
+
   AvoidNullChecksInEqualityOperators()
       : super(
             name: 'avoid_null_checks_in_equality_operators',
@@ -81,17 +75,55 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this);
+    var visitor =
+        _Visitor(this, nnbdEnabled: context.isEnabled(Feature.non_nullable));
     registry.addMethodDeclaration(this, visitor);
   }
 }
 
+class _BodyVisitor extends RecursiveAstVisitor {
+  final Element? parameter;
+  final LintRule rule;
+  _BodyVisitor(this.parameter, this.rule);
+
+  @override
+  visitBinaryExpression(BinaryExpression node) {
+    if (_isParameterWithQuestionQuestion(node, parameter) ||
+        _isComparingParameterWithNull(node, parameter)) {
+      rule.reportLint(node);
+    }
+    super.visitBinaryExpression(node);
+  }
+
+  @override
+  visitMethodInvocation(MethodInvocation node) {
+    if (node.operator?.type == TokenType.QUESTION_PERIOD &&
+        node.target.canonicalElement == parameter) {
+      rule.reportLint(node);
+    }
+    super.visitMethodInvocation(node);
+  }
+
+  @override
+  visitPropertyAccess(PropertyAccess node) {
+    if (node.operator.type == TokenType.QUESTION_PERIOD &&
+        node.target.canonicalElement == parameter) {
+      rule.reportLint(node);
+    }
+    super.visitPropertyAccess(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
+  final bool nnbdEnabled;
 
-  _Visitor(this.rule);
+  _Visitor(this.rule, {required this.nnbdEnabled});
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
@@ -99,18 +131,21 @@
     if (parameters == null) {
       return;
     }
-    if (node.name.token.type == TokenType.EQ_EQ && parameters.length == 1) {
-      var parameter = DartTypeUtilities.getCanonicalElementFromIdentifier(
-          parameters.first.identifier);
-      bool checkIfParameterIsNull(AstNode node) =>
-          _isParameterWithQuestion(node, parameter) ||
-          (node is BinaryExpression &&
-              (_isParameterWithQuestionQuestion(node, parameter) ||
-                  _isComparingParameterWithNull(node, parameter)));
 
-      DartTypeUtilities.traverseNodesInDFS(node.body)
-          .where(checkIfParameterIsNull)
-          .forEach(rule.reportLint);
+    if (node.name.type != TokenType.EQ_EQ || parameters.length != 1) {
+      return;
     }
+
+    var parameter = parameters.first.declaredElement?.canonicalElement;
+
+    // Analyzer will produce UNNECESSARY_NULL_COMPARISON_FALSE|TRUE
+    // See: https://github.com/dart-lang/linter/issues/2864
+    if (nnbdEnabled &&
+        parameter is VariableElement &&
+        parameter.type.nullabilitySuffix != NullabilitySuffix.question) {
+      return;
+    }
+
+    node.body.accept(_BodyVisitor(parameter, rule));
   }
 }
diff --git a/lib/src/rules/avoid_positional_boolean_parameters.dart b/lib/src/rules/avoid_positional_boolean_parameters.dart
index c13e39d..8145ed5 100644
--- a/lib/src/rules/avoid_positional_boolean_parameters.dart
+++ b/lib/src/rules/avoid_positional_boolean_parameters.dart
@@ -5,14 +5,15 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:collection/collection.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Avoid positional boolean parameters.';
 
 const _details = r'''
-
 **AVOID** positional boolean parameters.
 
 Positional boolean parameters are a bad practice because they are very
@@ -37,8 +38,11 @@
 
 ''';
 
-class AvoidPositionalBooleanParameters extends LintRule
-    implements NodeLintRule {
+class AvoidPositionalBooleanParameters extends LintRule {
+  static const LintCode code = LintCode('avoid_positional_boolean_parameters',
+      "'bool' parameters should be named parameters.",
+      correctionMessage: 'Try converting the parameter to a named parameter.');
+
   AvoidPositionalBooleanParameters()
       : super(
             name: 'avoid_positional_boolean_parameters',
@@ -47,10 +51,12 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
-    registry.addCompilationUnit(this, visitor);
     registry.addConstructorDeclaration(this, visitor);
     registry.addFunctionDeclaration(this, visitor);
     registry.addMethodDeclaration(this, visitor);
@@ -63,15 +69,18 @@
 
   _Visitor(this.rule, this.context);
 
+  void checkParams(List<FormalParameter>? parameters) {
+    var parameterToLint = parameters?.firstWhereOrNull(_isBoolean);
+    if (parameterToLint != null) {
+      rule.reportLint(parameterToLint);
+    }
+  }
+
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
     var declaredElement = node.declaredElement;
     if (declaredElement != null && !declaredElement.isPrivate) {
-      var parametersToLint =
-          node.parameters.parameters.where(_isFormalParameterToLint);
-      if (parametersToLint.isNotEmpty == true) {
-        rule.reportLint(parametersToLint.first);
-      }
+      checkParams(node.parameters.parameters);
     }
   }
 
@@ -79,11 +88,7 @@
   void visitFunctionDeclaration(FunctionDeclaration node) {
     var declaredElement = node.declaredElement;
     if (declaredElement != null && !declaredElement.isPrivate) {
-      var parametersToLint = node.functionExpression.parameters?.parameters
-          .where(_isFormalParameterToLint);
-      if (parametersToLint != null && parametersToLint.isNotEmpty) {
-        rule.reportLint(parametersToLint.first);
-      }
+      checkParams(node.functionExpression.parameters?.parameters);
     }
   }
 
@@ -94,37 +99,27 @@
         !node.isSetter &&
         !declaredElement.isPrivate &&
         !node.isOperator &&
-        !DartTypeUtilities.hasInheritedMethod(node) &&
+        !node.hasInheritedMethod &&
         !_isOverridingMember(declaredElement)) {
-      var parametersToLint =
-          node.parameters?.parameters.where(_isFormalParameterToLint);
-      if (parametersToLint != null && parametersToLint.isNotEmpty) {
-        rule.reportLint(parametersToLint.first);
-      }
+      checkParams(node.parameters?.parameters);
     }
   }
 
-  bool _isFormalParameterToLint(FormalParameter node) =>
-      !node.isNamed &&
-      DartTypeUtilities.isClass(
-          node.declaredElement?.type, 'bool', 'dart.core');
-
-  bool _isOverridingMember(Element? member) {
-    if (member == null) {
-      return false;
-    }
-
+  bool _isOverridingMember(Element member) {
     var classElement = member.thisOrAncestorOfType<ClassElement>();
-    if (classElement == null) {
-      return false;
-    }
+    if (classElement == null) return false;
+
     var name = member.name;
-    if (name == null) {
-      return false;
-    }
+    if (name == null) return false;
+
     var libraryUri = classElement.library.source.uri;
     return context.inheritanceManager
             .getInherited(classElement.thisType, Name(libraryUri, name)) !=
         null;
   }
+
+  static bool _isBoolean(FormalParameter node) {
+    var type = node.declaredElement?.type;
+    return !node.isNamed && type is InterfaceType && type.isDartCoreBool;
+  }
 }
diff --git a/lib/src/rules/avoid_print.dart b/lib/src/rules/avoid_print.dart
index e10bd6b..1410983 100644
--- a/lib/src/rules/avoid_print.dart
+++ b/lib/src/rules/avoid_print.dart
@@ -7,12 +7,18 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
+import '../ast.dart';
+import '../util/flutter_utils.dart';
 
 const _desc = r'Avoid `print` calls in production code.';
 
 const _details = r'''
 **DO** avoid `print` calls in production code.
 
+For production code, consider using a logging framework.
+If you are using Flutter, you can use `debugPrint`
+or surround `print` calls with a check for `kDebugMode`
+
 **BAD:**
 ```dart
 void f(int x) {
@@ -20,9 +26,42 @@
   ...
 }
 ```
+
+
+**GOOD:**
+```dart
+void f(int x) {
+  debugPrint('debug: $x');
+  ...
+}
+```
+
+
+**GOOD:**
+```dart
+void f(int x) {
+  log('log: $x');
+  ...
+}
+```
+
+
+**GOOD:**
+```dart
+void f(int x) {
+  if (kDebugMode) {
+      print('debug: $x');
+  }
+  ...
+}
+```
 ''';
 
-class AvoidPrint extends LintRule implements NodeLintRule {
+class AvoidPrint extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_print', "Don't invoke 'print' in production code.",
+      correctionMessage: 'Try using a logging framework.');
+
   AvoidPrint()
       : super(
             name: 'avoid_print',
@@ -31,6 +70,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -43,6 +85,33 @@
 
   _Visitor(this.rule);
 
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    if (node.methodName.staticElement.isDartCorePrint && !_isDebugOnly(node)) {
+      rule.reportLint(node.methodName);
+    }
+
+    node.argumentList.arguments.forEach(_validateArgument);
+  }
+
+  bool _isDebugOnly(Expression expression) {
+    AstNode? node = expression;
+    while (node != null) {
+      var parent = node.parent;
+      if (parent is IfStatement && node == parent.thenStatement) {
+        var condition = parent.condition;
+        if (condition is SimpleIdentifier &&
+            isKDebugMode(condition.staticElement)) {
+          return true;
+        }
+      } else if (parent is FunctionBody) {
+        return false;
+      }
+      node = parent;
+    }
+    return false;
+  }
+
   void _validateArgument(Expression expression) {
     if (expression is SimpleIdentifier) {
       var element = expression.staticElement;
@@ -53,16 +122,4 @@
       }
     }
   }
-
-  @override
-  void visitMethodInvocation(MethodInvocation node) {
-    bool isDartCore(MethodInvocation node) =>
-        node.methodName.staticElement?.library?.name == 'dart.core';
-
-    if (node.methodName.name == 'print' && isDartCore(node)) {
-      rule.reportLint(node.methodName);
-    }
-
-    node.argumentList.arguments.forEach(_validateArgument);
-  }
 }
diff --git a/lib/src/rules/avoid_private_typedef_functions.dart b/lib/src/rules/avoid_private_typedef_functions.dart
index ba30e5b..ad8a042 100644
--- a/lib/src/rules/avoid_private_typedef_functions.dart
+++ b/lib/src/rules/avoid_private_typedef_functions.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
@@ -10,7 +11,6 @@
 const _desc = r'Avoid private typedef functions.';
 
 const _details = r'''
-
 **AVOID** private typedef functions used only once. Prefer inline function
 syntax.
 
@@ -27,7 +27,11 @@
 
 ''';
 
-class AvoidPrivateTypedefFunctions extends LintRule implements NodeLintRule {
+class AvoidPrivateTypedefFunctions extends LintRule {
+  static const LintCode code = LintCode('avoid_private_typedef_functions',
+      'The typedef is unnecessary because it is only used in one place.',
+      correctionMessage: 'Try inlining the type or using it in other places.');
+
   AvoidPrivateTypedefFunctions()
       : super(
             name: 'avoid_private_typedef_functions',
@@ -36,6 +40,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -50,9 +57,9 @@
   _CountVisitor(this.type);
 
   @override
-  void visitTypeName(TypeName node) {
+  void visitNamedType(NamedType node) {
     if (node.name.name == type) count++;
-    super.visitTypeName(node);
+    super.visitNamedType(node);
   }
 }
 
@@ -70,23 +77,22 @@
 
   @override
   void visitGenericTypeAlias(GenericTypeAlias node) {
-    if (node.typeParameters != null) {
-      return;
-    }
+    if (node.typeParameters != null) return;
+    if (node.type is NamedType) return;
+
     _countAndReport(node.name);
   }
 
-  void _countAndReport(SimpleIdentifier identifier) {
-    var name = identifier.name;
-    if (!Identifier.isPrivateName(name)) {
-      return;
-    }
+  void _countAndReport(Token identifier) {
+    var name = identifier.lexeme;
+    if (!Identifier.isPrivateName(name)) return;
+
     var visitor = _CountVisitor(name);
     for (var unit in context.allUnits) {
       unit.unit.accept(visitor);
     }
     if (visitor.count <= 1) {
-      rule.reportLint(identifier);
+      rule.reportLintForToken(identifier);
     }
   }
 }
diff --git a/lib/src/rules/avoid_redundant_argument_values.dart b/lib/src/rules/avoid_redundant_argument_values.dart
index 330a997..7621792 100644
--- a/lib/src/rules/avoid_redundant_argument_values.dart
+++ b/lib/src/rules/avoid_redundant_argument_values.dart
@@ -4,19 +4,20 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:collection/collection.dart';
 
 import '../analyzer.dart';
 
 const _desc = r'Avoid redundant argument values.';
 
-const _details = r'''Avoid redundant argument values.
-
-**DON'T** declare arguments with values that match the defaults for the
-corresponding parameter.
+const _details = r'''
+**DON'T** pass an argument that matches the corresponding parameter's default
+value.
 
 **BAD:**
 ```dart
-void f({bool valWithDefault = true, bool val}) {
+void f({bool valWithDefault = true, bool? val}) {
   ...
 }
 
@@ -27,7 +28,7 @@
 
 **GOOD:**
 ```dart
-void f({bool valWithDefault = true, bool val}) {
+void f({bool valWithDefault = true, bool? val}) {
   ...
 }
 
@@ -38,7 +39,13 @@
 ```
 ''';
 
-class AvoidRedundantArgumentValues extends LintRule implements NodeLintRule {
+class AvoidRedundantArgumentValues extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_redundant_argument_values',
+      'The value of the argument is redundant because it matches the default '
+          'value.',
+      correctionMessage: 'Try removing the argument.');
+
   AvoidRedundantArgumentValues()
       : super(
             name: 'avoid_redundant_argument_values',
@@ -47,10 +54,15 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
+    registry.addEnumConstantArguments(this, visitor);
     registry.addInstanceCreationExpression(this, visitor);
+    registry.addFunctionExpressionInvocation(this, visitor);
     registry.addMethodInvocation(this, visitor);
   }
 }
@@ -70,30 +82,98 @@
     for (var i = arguments.length - 1; i >= 0; --i) {
       var arg = arguments[i];
       var param = arg.staticParameterElement;
-      if (param == null || param.hasRequired || param.isRequiredNamed) {
-        continue;
-      } else if (param.isRequiredPositional) {
-        break;
+      if (arg is NamedExpression) {
+        arg = arg.expression;
       }
-      var value = param.computeConstantValue();
-      if (value != null) {
-        if (arg is NamedExpression) {
-          arg = arg.expression;
-        }
-        var expressionValue = context.evaluateConstant(arg);
-        if (expressionValue.value == value) {
-          rule.reportLint(arg);
-        }
-      }
-      if (param.isOptionalPositional) {
+      checkArgument(arg, param);
+      if (param != null && param.isOptionalPositional) {
+        // Redundant arguments may be necessary to specify, in order to specify
+        // a non-redundant argument for the last optional positional parameter.
         break;
       }
     }
   }
 
+  void checkArgument(Expression arg, ParameterElement? param) {
+    if (param == null ||
+        param.declaration.isRequired ||
+        param.hasRequired ||
+        !param.isOptional) {
+      return;
+    }
+    var value = param.computeConstantValue();
+    if (value != null && value.hasKnownValue) {
+      var expressionValue = context.evaluateConstant(arg).value;
+      if ((expressionValue?.hasKnownValue ?? false) &&
+          expressionValue == value) {
+        rule.reportLint(arg);
+      }
+    }
+  }
+
+  @override
+  void visitEnumConstantArguments(EnumConstantArguments node) {
+    check(node.argumentList);
+  }
+
+  @override
+  visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
+    check(node.argumentList);
+  }
+
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
-    check(node.argumentList);
+    var constructor = node.constructorName.staticElement;
+    if (constructor != null && !constructor.isFactory) {
+      check(node.argumentList);
+      return;
+    }
+
+    var redirectedConstructor = constructor?.redirectedConstructor;
+    while (redirectedConstructor?.redirectedConstructor != null) {
+      redirectedConstructor = redirectedConstructor?.redirectedConstructor;
+    }
+    if (redirectedConstructor == null) {
+      check(node.argumentList);
+      return;
+    }
+
+    var parameters = redirectedConstructor.parameters;
+
+    // If the constructor being called is a redirecting factory constructor, an
+    // argument is redundant if it is equal to the default value of the
+    // corresponding parameter on the _redirectied constructor_, not this
+    // constructor, which may be different.
+
+    var arguments = node.argumentList.arguments;
+    if (arguments.isEmpty) {
+      return;
+    }
+
+    for (var i = arguments.length - 1; i >= 0; --i) {
+      var arg = arguments[i];
+      ParameterElement? param;
+      if (arg is NamedExpression) {
+        param = parameters.firstWhereOrNull(
+            (p) => p.isNamed && p.name == arg.name.label.name);
+      } else {
+        // Count which positional argument we're at.
+        var positionalCount =
+            arguments.take(i + 1).where((a) => a is! NamedExpression).length;
+        var positionalIndex = positionalCount - 1;
+        if (positionalIndex < parameters.length) {
+          if (parameters[positionalIndex].isPositional) {
+            param = parameters[positionalIndex];
+          }
+        }
+      }
+      checkArgument(arg, param);
+      if (param != null && param.isOptionalPositional) {
+        // Redundant arguments may be necessary to specify, in order to specify
+        // a non-redundant argument for the last optional positional parameter.
+        break;
+      }
+    }
   }
 
   @override
diff --git a/lib/src/rules/avoid_relative_lib_imports.dart b/lib/src/rules/avoid_relative_lib_imports.dart
index 5fa7420..e1e4788 100644
--- a/lib/src/rules/avoid_relative_lib_imports.dart
+++ b/lib/src/rules/avoid_relative_lib_imports.dart
@@ -9,25 +9,18 @@
 
 const _desc = r'Avoid relative imports for files in `lib/`.';
 
-const _details = r'''*DO* avoid relative imports for files in `lib/`.
+const _details = r'''
+**DO** avoid relative imports for files in `lib/`.
 
 When mixing relative and absolute imports it's possible to create confusion
 where the same member gets imported in two different ways.  An easy way to avoid
 that is to ensure you have no relative imports that include `lib/` in their
 paths.
 
-**GOOD:**
-
-```dart
-import 'package:foo/bar.dart';
-
-import 'baz.dart';
-
-...
-```
+You can also use 'always_use_package_imports' to disallow relative imports
+between files within `lib/`.
 
 **BAD:**
-
 ```dart
 import 'package:foo/bar.dart';
 
@@ -36,9 +29,27 @@
 ...
 ```
 
+**GOOD:**
+```dart
+import 'package:foo/bar.dart';
+
+import 'baz.dart';
+
+...
+```
+
 ''';
 
-class AvoidRelativeLibImports extends LintRule implements NodeLintRule {
+class AvoidRelativeLibImports extends LintRule {
+  static const LintCode code = LintCode('avoid_relative_lib_imports',
+      "Can't use a relative path to import a library in 'lib'.",
+      correctionMessage:
+          "Try fixing the relative path or changing the import to a 'package:' "
+          'import.');
+
+  @override
+  LintCode get lintCode => code;
+
   AvoidRelativeLibImports()
       : super(
             name: 'avoid_relative_lib_imports',
@@ -60,10 +71,9 @@
   _Visitor(this.rule);
 
   bool isRelativeLibImport(ImportDirective node) {
-    // This check is too narrow.  Really we should be checking against the
-    // resolved URI and not it's literal string content.
-    // See: https://github.com/dart-lang/linter/issues/2419
-    var uriContent = node.uriContent;
+    // Relative paths from within the `lib` folder are covered by the
+    // `always_use_package_imports` lint.
+    var uriContent = node.uri.stringValue;
     if (uriContent != null) {
       var uri = Uri.tryParse(uriContent);
       if (uri != null && uri.scheme.isEmpty) {
diff --git a/lib/src/rules/avoid_renaming_method_parameters.dart b/lib/src/rules/avoid_renaming_method_parameters.dart
index 54e9c62..c8eebe9 100644
--- a/lib/src/rules/avoid_renaming_method_parameters.dart
+++ b/lib/src/rules/avoid_renaming_method_parameters.dart
@@ -13,12 +13,13 @@
 
 const _desc = r"Don't rename parameters of overridden methods.";
 
-const _details = r'''**DON'T** rename parameters of overridden methods.
+const _details = r'''
+**DON'T** rename parameters of overridden methods.
 
 Methods that override another method, but do not have their own documentation
-comment, will inherit the overridden method's comment when dartdoc produces
+comment, will inherit the overridden method's comment when `dart doc` produces
 documentation. If the inherited method contains the name of the parameter (in
-square brackets), then dartdoc cannot link it correctly.
+square brackets), then `dart doc` cannot link it correctly.
 
 **BAD:**
 ```dart
@@ -44,7 +45,7 @@
 
 ''';
 
-class AvoidRenamingMethodParameters extends LintRule implements NodeLintRule {
+class AvoidRenamingMethodParameters extends LintRule {
   AvoidRenamingMethodParameters()
       : super(
             name: 'avoid_renaming_method_parameters',
@@ -53,22 +54,30 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => _Visitor.parameterCode;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     if (!isInLibDir(context.currentUnit.unit, context.package)) {
       return;
     }
 
-    var visitor = _Visitor(this, context);
+    var visitor = _Visitor(this);
     registry.addMethodDeclaration(this, visitor);
   }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  final LintRule rule;
-  final LinterContext context;
+  static const LintCode parameterCode = LintCode(
+      'avoid_renaming_method_parameters',
+      "The parameter name '{0}' doesn't match the name '{1}' in the overridden "
+          'method.',
+      correctionMessage: "Try changing the name to '{1}'.");
 
-  _Visitor(this.rule, this.context);
+  final LintRule rule;
+
+  _Visitor(this.rule);
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
@@ -81,7 +90,7 @@
     }
     var parentElement = parentNode.declaredElement;
     // Note: there are no override semantics with extension methods.
-    if (parentElement is! ClassElement) {
+    if (parentElement is! InterfaceElement) {
       return;
     }
 
@@ -90,7 +99,7 @@
     if (classElement.isPrivate) return;
 
     var parentMethod = classElement.lookUpInheritedMethod(
-        node.name.name, classElement.library);
+        node.name.lexeme, classElement.library);
 
     if (parentMethod == null) return;
 
@@ -105,10 +114,12 @@
     var count = math.min(parameters.length, parentParameters.length);
     for (var i = 0; i < count; i++) {
       if (parentParameters.length <= i) break;
-      var paramIdentifier = parameters[i].identifier;
+      var paramIdentifier = parameters[i].name;
       if (paramIdentifier != null &&
-          paramIdentifier.name != parentParameters[i].name) {
-        rule.reportLint(parameters[i].identifier);
+          paramIdentifier.lexeme != parentParameters[i].name) {
+        rule.reportLintForToken(paramIdentifier,
+            arguments: [paramIdentifier.lexeme, parentParameters[i].name],
+            errorCode: parameterCode);
       }
     }
   }
diff --git a/lib/src/rules/avoid_return_types_on_setters.dart b/lib/src/rules/avoid_return_types_on_setters.dart
index 623b26e..6a2bc6f 100644
--- a/lib/src/rules/avoid_return_types_on_setters.dart
+++ b/lib/src/rules/avoid_return_types_on_setters.dart
@@ -10,24 +10,27 @@
 const _desc = r'Avoid return types on setters.';
 
 const _details = r'''
-
 **AVOID** return types on setters.
 
 As setters do not return a value, declaring the return type of one is redundant.
 
-**GOOD:**
-```dart
-set speed(int ms);
-```
-
 **BAD:**
 ```dart
 void set speed(int ms);
 ```
 
+**GOOD:**
+```dart
+set speed(int ms);
+```
+
 ''';
 
-class AvoidReturnTypesOnSetters extends LintRule implements NodeLintRule {
+class AvoidReturnTypesOnSetters extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_return_types_on_setters', 'Unnecessary return type on a setter.',
+      correctionMessage: 'Try removing the return type.');
+
   AvoidReturnTypesOnSetters()
       : super(
             name: 'avoid_return_types_on_setters',
@@ -36,6 +39,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/avoid_returning_null.dart b/lib/src/rules/avoid_returning_null.dart
index 7f29af9..8a01e61 100644
--- a/lib/src/rules/avoid_returning_null.dart
+++ b/lib/src/rules/avoid_returning_null.dart
@@ -8,14 +8,13 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc =
     r'Avoid returning null from members whose return type is bool, double, int,'
     r' or num.';
 
 const _details = r'''
-
 **AVOID** returning null from members whose return type is bool, double, int,
 or num.
 
@@ -41,18 +40,20 @@
 
 ''';
 
-bool _isFunctionExpression(AstNode node) => node is FunctionExpression;
-
 bool _isPrimitiveType(DartType type) =>
-    DartTypeUtilities.isClass(type, 'bool', 'dart.core') ||
-    DartTypeUtilities.isClass(type, 'num', 'dart.core') ||
-    DartTypeUtilities.isClass(type, 'int', 'dart.core') ||
-    DartTypeUtilities.isClass(type, 'double', 'dart.core');
+    type is InterfaceType &&
+    (type.isDartCoreBool ||
+        type.isDartCoreDouble ||
+        type.isDartCoreInt ||
+        type.isDartCoreNum);
 
-bool _isReturnNull(AstNode node) =>
-    node is ReturnStatement && DartTypeUtilities.isNullLiteral(node.expression);
+class AvoidReturningNull extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_returning_null',
+      "Don't return 'null' when the return type is 'bool', 'double', 'int', "
+          "or 'num'.",
+      correctionMessage: "Try returning a sentinel value other than 'null'.");
 
-class AvoidReturningNull extends LintRule implements NodeLintRule {
   AvoidReturningNull()
       : super(
             name: 'avoid_returning_null',
@@ -61,6 +62,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     // This lint does not make sense in the context of nullability.
@@ -74,6 +78,25 @@
   }
 }
 
+class _BodyVisitor extends RecursiveAstVisitor {
+  final LintRule rule;
+  _BodyVisitor(this.rule);
+
+  @override
+  visitFunctionExpression(FunctionExpression node) {
+    // Skip Function expressions.
+  }
+
+  @override
+  visitReturnStatement(ReturnStatement node) {
+    if (node.expression.isNullLiteral) {
+      rule.reportLint(node);
+    }
+
+    super.visitReturnStatement(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
@@ -97,15 +120,12 @@
     }
   }
 
-  void _visitFunctionBody(FunctionBody? node) {
-    if (node is ExpressionFunctionBody &&
-        DartTypeUtilities.isNullLiteral(node.expression)) {
+  void _visitFunctionBody(FunctionBody node) {
+    if (node is ExpressionFunctionBody && node.expression.isNullLiteral) {
       rule.reportLint(node);
       return;
     }
-    DartTypeUtilities.traverseNodesInDFS(node!,
-            excludeCriteria: _isFunctionExpression)
-        .where(_isReturnNull)
-        .forEach(rule.reportLint);
+
+    node.accept(_BodyVisitor(rule));
   }
 }
diff --git a/lib/src/rules/avoid_returning_null_for_future.dart b/lib/src/rules/avoid_returning_null_for_future.dart
index d578196..fb92063 100644
--- a/lib/src/rules/avoid_returning_null_for_future.dart
+++ b/lib/src/rules/avoid_returning_null_for_future.dart
@@ -12,7 +12,6 @@
 const _desc = r'Avoid returning null for Future.';
 
 const _details = r'''
-
 **AVOID** returning null for Future.
 
 It is almost always wrong to return `null` for a `Future`.  Most of the time the
@@ -20,7 +19,12 @@
 
 ''';
 
-class AvoidReturningNullForFuture extends LintRule implements NodeLintRule {
+class AvoidReturningNullForFuture extends LintRule {
+  static const LintCode code = LintCode('avoid_returning_null_for_future',
+      "Don't return 'null' when the return type is 'Future'.",
+      correctionMessage:
+          "Try making the function 'async', or returning 'Future.value(null)'.");
+
   AvoidReturningNullForFuture()
       : super(
             name: 'avoid_returning_null_for_future',
@@ -29,6 +33,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     // In a Null Safety library, this lint is covered by other formal static
diff --git a/lib/src/rules/avoid_returning_null_for_void.dart b/lib/src/rules/avoid_returning_null_for_void.dart
index c756693..2d5009f 100644
--- a/lib/src/rules/avoid_returning_null_for_void.dart
+++ b/lib/src/rules/avoid_returning_null_for_void.dart
@@ -11,7 +11,6 @@
 const _desc = r'Avoid returning null for void.';
 
 const _details = r'''
-
 **AVOID** returning null for void.
 
 In a large variety of languages `void` as return type is used to indicate that
@@ -42,7 +41,15 @@
 
 ''';
 
-class AvoidReturningNullForVoid extends LintRule implements NodeLintRule {
+class AvoidReturningNullForVoid extends LintRule {
+  static const LintCode fromFunction = LintCode('avoid_returning_null_for_void',
+      "Don't return 'null' from a function with a return type of 'void'.",
+      correctionMessage: "Try removing the 'null'.");
+
+  static const LintCode fromMethod = LintCode('avoid_returning_null_for_void',
+      "Don't return 'null' from a method with a return type of 'void'.",
+      correctionMessage: "Try removing the 'null'.");
+
   AvoidReturningNullForVoid()
       : super(
             name: 'avoid_returning_null_for_void',
@@ -51,6 +58,9 @@
             group: Group.style);
 
   @override
+  List<LintCode> get lintCodes => [fromFunction, fromMethod];
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -87,23 +97,26 @@
 
     DartType? type;
     bool? isAsync;
+    LintCode code;
     if (parent is FunctionExpression) {
       type = parent.declaredElement?.returnType;
       isAsync = parent.body.isAsynchronous;
+      code = AvoidReturningNullForVoid.fromFunction;
     } else if (parent is MethodDeclaration) {
       type = parent.declaredElement?.returnType;
       isAsync = parent.body.isAsynchronous;
+      code = AvoidReturningNullForVoid.fromMethod;
     } else {
       throw StateError('unexpected type');
     }
     if (type == null) return;
 
     if (!isAsync && type.isVoid) {
-      rule.reportLint(node);
+      rule.reportLint(node, errorCode: code);
     } else if (isAsync &&
         type.isDartAsyncFuture &&
         (type as InterfaceType).typeArguments.first.isVoid) {
-      rule.reportLint(node);
+      rule.reportLint(node, errorCode: code);
     }
   }
 }
diff --git a/lib/src/rules/avoid_returning_this.dart b/lib/src/rules/avoid_returning_this.dart
index 2e47597..53d12f4 100644
--- a/lib/src/rules/avoid_returning_this.dart
+++ b/lib/src/rules/avoid_returning_this.dart
@@ -7,13 +7,12 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc =
     r'Avoid returning this from methods just to enable a fluent interface.';
 
 const _details = r'''
-
 **AVOID** returning this from methods just to enable a fluent interface.
 
 Returning `this` from a method is redundant; Dart has a cascade operator which
@@ -44,14 +43,14 @@
 
 ''';
 
-bool _isFunctionExpression(AstNode node) => node is FunctionExpression;
+bool _returnsThis(ReturnStatement node) => node.expression is ThisExpression;
 
-bool _isReturnStatement(AstNode node) => node is ReturnStatement;
+class AvoidReturningThis extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_returning_this', "Don't return 'this' from a method.",
+      correctionMessage:
+          "Try changing the return type to 'void' and removing the return.");
 
-bool _returnsThis(AstNode node) =>
-    (node as ReturnStatement).expression is ThisExpression;
-
-class AvoidReturningThis extends LintRule implements NodeLintRule {
   AvoidReturningThis()
       : super(
             name: 'avoid_returning_this',
@@ -60,6 +59,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -67,6 +69,29 @@
   }
 }
 
+class _BodyVisitor extends RecursiveAstVisitor {
+  List<ReturnStatement> returnStatements = [];
+
+  List<ReturnStatement> collectReturns(BlockFunctionBody body) {
+    body.accept(this);
+    return returnStatements;
+  }
+
+  @override
+  visitFunctionExpression(FunctionExpression node) {
+    // Short-circuit visiting on Function expressions.
+  }
+
+  @override
+  visitReturnStatement(ReturnStatement node) {
+    // Short-circuit if not returning this.
+    if (!_returnsThis(node)) return;
+
+    returnStatements.add(node);
+    super.visitReturnStatement(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
@@ -77,14 +102,17 @@
     if (node.isOperator) return;
 
     var parent = node.parent;
-    if (parent is ClassOrMixinDeclaration) {
-      if (DartTypeUtilities.overridesMethod(node)) {
+    if (parent is ClassDeclaration ||
+        parent is EnumDeclaration ||
+        parent is MixinDeclaration) {
+      if (node.isOverride) {
         return;
       }
 
       var returnType = node.declaredElement?.returnType;
       if (returnType is InterfaceType &&
-          returnType.element == parent.declaredElement) {
+          // ignore: cast_nullable_to_non_nullable
+          returnType.element == (parent as Declaration).declaredElement) {
       } else {
         return;
       }
@@ -95,15 +123,13 @@
 
     var body = node.body;
     if (body is BlockFunctionBody) {
-      var returnStatements = DartTypeUtilities.traverseNodesInDFS(body.block,
-              excludeCriteria: _isFunctionExpression)
-          .where(_isReturnStatement);
-      if (returnStatements.isNotEmpty && returnStatements.every(_returnsThis)) {
-        rule.reportLint(node.name);
+      var returnStatements = _BodyVisitor().collectReturns(body);
+      if (returnStatements.isNotEmpty) {
+        rule.reportLint(returnStatements.first.expression);
       }
     } else if (body is ExpressionFunctionBody) {
       if (body.expression is ThisExpression) {
-        rule.reportLint(node.name);
+        rule.reportLintForToken(node.name);
       }
     }
   }
diff --git a/lib/src/rules/avoid_setters_without_getters.dart b/lib/src/rules/avoid_setters_without_getters.dart
index 4aabeba..cc13609 100644
--- a/lib/src/rules/avoid_setters_without_getters.dart
+++ b/lib/src/rules/avoid_setters_without_getters.dart
@@ -6,12 +6,11 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Avoid setters without getters.';
 
 const _details = r'''
-
 **DON'T** define a setter without a corresponding getter.
 
 Defining a setter without defining a corresponding getter can lead to logical
@@ -44,13 +43,12 @@
 
 ''';
 
-bool _hasGetter(MethodDeclaration node) =>
-    DartTypeUtilities.lookUpGetter(node) != null;
+class AvoidSettersWithoutGetters extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_setters_without_getters', 'Setter has no corresponding getter.',
+      correctionMessage:
+          'Try adding a corresponding getter or removing the setter.');
 
-bool _hasInheritedSetter(MethodDeclaration node) =>
-    DartTypeUtilities.lookUpInheritedConcreteSetter(node) != null;
-
-class AvoidSettersWithoutGetters extends LintRule implements NodeLintRule {
   AvoidSettersWithoutGetters()
       : super(
             name: 'avoid_setters_without_getters',
@@ -59,10 +57,15 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
     registry.addClassDeclaration(this, visitor);
+    registry.addEnumDeclaration(this, visitor);
+    // todo(pq): consider visiting mixin declarations
   }
 }
 
@@ -73,11 +76,20 @@
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
-    for (var member in node.members.whereType<MethodDeclaration>()) {
+    visitMembers(node.members);
+  }
+
+  @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    visitMembers(node.members);
+  }
+
+  void visitMembers(NodeList<ClassMember> members) {
+    for (var member in members.whereType<MethodDeclaration>()) {
       if (member.isSetter &&
-          !_hasInheritedSetter(member) &&
-          !_hasGetter(member)) {
-        rule.reportLint(member.name);
+          member.lookUpInheritedConcreteSetter() == null &&
+          member.lookUpGetter() == null) {
+        rule.reportLintForToken(member.name);
       }
     }
   }
diff --git a/lib/src/rules/avoid_shadowing_type_parameters.dart b/lib/src/rules/avoid_shadowing_type_parameters.dart
index 3bf9b52..a8bb0f6 100644
--- a/lib/src/rules/avoid_shadowing_type_parameters.dart
+++ b/lib/src/rules/avoid_shadowing_type_parameters.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid shadowing type parameters.';
 
 const _details = r'''
-
 **AVOID** shadowing type parameters.
 
 **BAD:**
@@ -29,7 +28,11 @@
 
 ''';
 
-class AvoidShadowingTypeParameters extends LintRule implements NodeLintRule {
+class AvoidShadowingTypeParameters extends LintRule {
+  static const LintCode code = LintCode('avoid_shadowing_type_parameters',
+      "The type parameter '{0}' shadows a type parameter from the enclosing {1}.",
+      correctionMessage: 'Try renaming one of the type parameters.');
+
   AvoidShadowingTypeParameters()
       : super(
             name: 'avoid_shadowing_type_parameters',
@@ -38,6 +41,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -64,7 +70,7 @@
   void visitGenericTypeAlias(GenericTypeAlias node) {
     var typeParameters = node.functionType?.typeParameters;
     if (typeParameters != null) {
-      _checkForShadowing(typeParameters, node.typeParameters);
+      _checkForShadowing(typeParameters, node.typeParameters, 'typedef');
     }
   }
 
@@ -86,15 +92,19 @@
     var parent = node.parent;
 
     while (parent != null) {
-      if (parent is ClassOrMixinDeclaration) {
-        _checkForShadowing(typeParameters, parent.typeParameters);
+      if (parent is ClassDeclaration) {
+        _checkForShadowing(typeParameters, parent.typeParameters, 'class');
+      } else if (parent is EnumDeclaration) {
+        _checkForShadowing(typeParameters, parent.typeParameters, 'enum');
       } else if (parent is ExtensionDeclaration) {
-        _checkForShadowing(typeParameters, parent.typeParameters);
+        _checkForShadowing(typeParameters, parent.typeParameters, 'extension');
       } else if (parent is MethodDeclaration) {
-        _checkForShadowing(typeParameters, parent.typeParameters);
+        _checkForShadowing(typeParameters, parent.typeParameters, 'method');
+      } else if (parent is MixinDeclaration) {
+        _checkForShadowing(typeParameters, parent.typeParameters, 'mixin');
       } else if (parent is FunctionDeclaration) {
-        _checkForShadowing(
-            typeParameters, parent.functionExpression.typeParameters);
+        _checkForShadowing(typeParameters,
+            parent.functionExpression.typeParameters, 'function');
       }
       parent = parent.parent;
     }
@@ -102,17 +112,20 @@
 
   // Check whether any of [typeParameters] shadow [ancestorTypeParameters].
   void _checkForShadowing(TypeParameterList? typeParameters,
-      TypeParameterList? ancestorTypeParameters) {
+      TypeParameterList? ancestorTypeParameters, String ancestorKind) {
     if (typeParameters == null || ancestorTypeParameters == null) {
       return;
     }
 
-    var typeParameterIds = typeParameters.typeParameters.map((tp) => tp.name);
-    var ancestorTypeParameterNames =
-        ancestorTypeParameters.typeParameters.map((tp) => tp.name.name);
-    var shadowingTypeParameters = typeParameterIds
-        .where((tp) => ancestorTypeParameterNames.contains(tp.name));
+    var ancestorTypeParameterNames = ancestorTypeParameters.typeParameters
+        .map((tp) => tp.name.lexeme)
+        .toSet();
 
-    shadowingTypeParameters.forEach(rule.reportLint);
+    for (var parameter in typeParameters.typeParameters) {
+      if (ancestorTypeParameterNames.contains(parameter.name.lexeme)) {
+        rule.reportLint(parameter,
+            arguments: [parameter.name.lexeme, ancestorKind]);
+      }
+    }
   }
 }
diff --git a/lib/src/rules/avoid_single_cascade_in_expression_statements.dart b/lib/src/rules/avoid_single_cascade_in_expression_statements.dart
index 4f39d6b..7339e9b 100644
--- a/lib/src/rules/avoid_single_cascade_in_expression_statements.dart
+++ b/lib/src/rules/avoid_single_cascade_in_expression_statements.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
@@ -10,7 +11,6 @@
 const _desc = r'Avoid single cascade in expression statements.';
 
 const _details = r'''
-
 **AVOID** single cascade in expression statements.
 
 **BAD:**
@@ -25,8 +25,12 @@
 
 ''';
 
-class AvoidSingleCascadeInExpressionStatements extends LintRule
-    implements NodeLintRule {
+class AvoidSingleCascadeInExpressionStatements extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_single_cascade_in_expression_statements',
+      'Unnecessary cascade expression.',
+      correctionMessage: "Try using the operator '{0}'.");
+
   AvoidSingleCascadeInExpressionStatements()
       : super(
             name: 'avoid_single_cascade_in_expression_statements',
@@ -35,6 +39,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -47,11 +54,25 @@
 
   _Visitor(this.rule);
 
+  String operatorFor(Expression section) {
+    Token? operator;
+    if (section is PropertyAccess) {
+      operator = section.operator;
+    } else if (section is MethodInvocation) {
+      operator = section.operator;
+    }
+    if (operator?.type == TokenType.PERIOD_PERIOD_PERIOD_QUESTION) {
+      return '?.';
+    }
+    return '.';
+  }
+
   @override
   void visitCascadeExpression(CascadeExpression node) {
-    if (node.cascadeSections.length == 1 &&
-        node.parent is ExpressionStatement) {
-      rule.reportLint(node);
+    var sections = node.cascadeSections;
+    if (sections.length == 1 && node.parent is ExpressionStatement) {
+      var operator = operatorFor(sections[0]);
+      rule.reportLint(node, arguments: [operator]);
     }
   }
 }
diff --git a/lib/src/rules/avoid_slow_async_io.dart b/lib/src/rules/avoid_slow_async_io.dart
index a28b081..5b3d746 100644
--- a/lib/src/rules/avoid_slow_async_io.dart
+++ b/lib/src/rules/avoid_slow_async_io.dart
@@ -8,12 +8,11 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Avoid slow async `dart:io` methods.';
 
 const _details = r'''
-
 **AVOID** using the following asynchronous file I/O methods because they are
 much slower than their synchronous counterparts.
 
@@ -51,13 +50,13 @@
 
 ''';
 
-const List<String> _fileMethodNames = <String>[
-  'lastModified',
+const List<String> _dirMethodNames = <String>[
   'exists',
   'stat',
 ];
 
-const List<String> _dirMethodNames = <String>[
+const List<String> _fileMethodNames = <String>[
+  'lastModified',
   'exists',
   'stat',
 ];
@@ -69,7 +68,11 @@
   'type',
 ];
 
-class AvoidSlowAsyncIo extends LintRule implements NodeLintRule {
+class AvoidSlowAsyncIo extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_slow_async_io', "Use of an async 'dart:io' method.",
+      correctionMessage: 'Try using the synchronous version of the method.');
+
   AvoidSlowAsyncIo()
       : super(
             name: 'avoid_slow_async_io',
@@ -78,6 +81,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -103,17 +109,17 @@
     }
   }
 
-  void _checkFileMethods(MethodInvocation node, DartType? type) {
-    if (DartTypeUtilities.extendsClass(type, 'File', 'dart.io')) {
-      if (_fileMethodNames.contains(node.methodName.name)) {
+  void _checkDirectoryMethods(MethodInvocation node, DartType? type) {
+    if (type.extendsClass('Directory', 'dart.io')) {
+      if (_dirMethodNames.contains(node.methodName.name)) {
         rule.reportLint(node);
       }
     }
   }
 
-  void _checkDirectoryMethods(MethodInvocation node, DartType? type) {
-    if (DartTypeUtilities.extendsClass(type, 'Directory', 'dart.io')) {
-      if (_dirMethodNames.contains(node.methodName.name)) {
+  void _checkFileMethods(MethodInvocation node, DartType? type) {
+    if (type.extendsClass('File', 'dart.io')) {
+      if (_fileMethodNames.contains(node.methodName.name)) {
         rule.reportLint(node);
       }
     }
diff --git a/lib/src/rules/avoid_type_to_string.dart b/lib/src/rules/avoid_type_to_string.dart
index 5765146..e78b431 100644
--- a/lib/src/rules/avoid_type_to_string.dart
+++ b/lib/src/rules/avoid_type_to_string.dart
@@ -13,7 +13,6 @@
     r'Avoid <Type>.toString() in production code since results may be minified.';
 
 const _details = r'''
-
 **DO** avoid calls to <Type>.toString() in production code, since it does not
 contractually return the user-defined name of the Type (or underlying class).
 Development-mode compilers where code size is not a concern use the full name,
@@ -51,7 +50,10 @@
 
 ''';
 
-class AvoidTypeToString extends LintRule implements NodeLintRule {
+class AvoidTypeToString extends LintRule {
+  static const LintCode code = LintCode('avoid_type_to_string',
+      "Using 'toString' on a 'Type' is not safe in production code.");
+
   AvoidTypeToString()
       : super(
             name: 'avoid_type_to_string',
@@ -60,6 +62,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor =
@@ -90,12 +95,12 @@
   _Visitor(this.rule, this.typeSystem, this.typeType);
 
   @override
-  void visitClassDeclaration(ClassDeclaration node) {
-    thisType = node.declaredElement?.thisType;
+  void visitArgumentList(ArgumentList node) {
+    node.arguments.forEach(_validateArgument);
   }
 
   @override
-  void visitMixinDeclaration(MixinDeclaration node) {
+  void visitClassDeclaration(ClassDeclaration node) {
     thisType = node.declaredElement?.thisType;
   }
 
@@ -116,8 +121,27 @@
   }
 
   @override
-  void visitArgumentList(ArgumentList node) {
-    node.arguments.forEach(_validateArgument);
+  void visitMixinDeclaration(MixinDeclaration node) {
+    thisType = node.declaredElement?.thisType;
+  }
+
+  bool _isSimpleIdDeclByCoreObj(SimpleIdentifier simpleIdentifier) {
+    var encloser = simpleIdentifier.staticElement?.enclosingElement;
+    return encloser is ClassElement && encloser.isDartCoreObject;
+  }
+
+  bool _isToStringOnCoreTypeClass(
+          InterfaceType? targetType, SimpleIdentifier methodIdentifier) =>
+      targetType != null &&
+      methodIdentifier.name == 'toString' &&
+      _isSimpleIdDeclByCoreObj(methodIdentifier) &&
+      typeSystem.isSubtypeOf(targetType, typeType);
+
+  void _reportIfToStringOnCoreTypeClass(
+      InterfaceType? targetType, SimpleIdentifier methodIdentifier) {
+    if (_isToStringOnCoreTypeClass(targetType, methodIdentifier)) {
+      rule.reportLint(methodIdentifier);
+    }
   }
 
   void _validateArgument(Expression expression) {
@@ -135,23 +159,4 @@
       _reportIfToStringOnCoreTypeClass(thisType, expression);
     }
   }
-
-  void _reportIfToStringOnCoreTypeClass(
-      InterfaceType? targetType, SimpleIdentifier methodIdentifier) {
-    if (_isToStringOnCoreTypeClass(targetType, methodIdentifier)) {
-      rule.reportLint(methodIdentifier);
-    }
-  }
-
-  bool _isToStringOnCoreTypeClass(
-          InterfaceType? targetType, SimpleIdentifier methodIdentifier) =>
-      targetType != null &&
-      methodIdentifier.name == 'toString' &&
-      _isSimpleIdDeclByCoreObj(methodIdentifier) &&
-      typeSystem.isSubtypeOf(targetType, typeType);
-
-  bool _isSimpleIdDeclByCoreObj(SimpleIdentifier simpleIdentifier) {
-    var encloser = simpleIdentifier.staticElement?.enclosingElement;
-    return encloser is ClassElement && encloser.isDartCoreObject;
-  }
 }
diff --git a/lib/src/rules/avoid_types_as_parameter_names.dart b/lib/src/rules/avoid_types_as_parameter_names.dart
index b1fd910..54a35ae 100644
--- a/lib/src/rules/avoid_types_as_parameter_names.dart
+++ b/lib/src/rules/avoid_types_as_parameter_names.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 
@@ -11,7 +12,6 @@
 const _desc = r'Avoid types as parameter names.';
 
 const _details = r'''
-
 **AVOID** using a parameter name that is the same as an existing type.
 
 **BAD:**
@@ -26,7 +26,13 @@
 
 ''';
 
-class AvoidTypesAsParameterNames extends LintRule implements NodeLintRule {
+class AvoidTypesAsParameterNames extends LintRule {
+  static const LintCode code = LintCode('avoid_types_as_parameter_names',
+      "The parameter name '{0}' matches a visible type name.",
+      correctionMessage:
+          'Try adding a name for the parameter or changing the parameter name '
+          'to not match an existing type.');
+
   AvoidTypesAsParameterNames()
       : super(
             name: 'avoid_types_as_parameter_names',
@@ -35,6 +41,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -52,8 +61,8 @@
   @override
   void visitCatchClause(CatchClause node) {
     var parameter = node.exceptionParameter;
-    if (parameter != null && _isTypeName(node, parameter)) {
-      rule.reportLint(parameter);
+    if (parameter != null && _isTypeName(node, parameter.name)) {
+      rule.reportLint(parameter, arguments: [parameter.name.lexeme]);
     }
   }
 
@@ -63,19 +72,19 @@
 
     for (var parameter in node.parameters) {
       var declaredElement = parameter.declaredElement;
-      var identifier = parameter.identifier;
+      var name = parameter.name;
       if (declaredElement != null &&
           declaredElement is! FieldFormalParameterElement &&
           declaredElement.hasImplicitType &&
-          identifier != null &&
-          _isTypeName(node, identifier)) {
-        rule.reportLint(identifier);
+          name != null &&
+          _isTypeName(node, name)) {
+        rule.reportLintForToken(name, arguments: [name.lexeme]);
       }
     }
   }
 
-  bool _isTypeName(AstNode scope, SimpleIdentifier node) {
-    var result = context.resolveNameInScope(node.name, false, scope);
+  bool _isTypeName(AstNode scope, Token name) {
+    var result = context.resolveNameInScope(name.lexeme, false, scope);
     if (result.isRequestedName) {
       var element = result.element;
       return element is ClassElement || element is TypeAliasElement;
diff --git a/lib/src/rules/avoid_types_on_closure_parameters.dart b/lib/src/rules/avoid_types_on_closure_parameters.dart
index 7ebf71c..8c3ae26 100644
--- a/lib/src/rules/avoid_types_on_closure_parameters.dart
+++ b/lib/src/rules/avoid_types_on_closure_parameters.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid annotating types for function expression parameters.';
 
 const _details = r'''
-
 **AVOID** annotating types for function expression parameters.
 
 Annotating types for function expression parameters is usually unnecessary
@@ -29,7 +28,11 @@
 
 ''';
 
-class AvoidTypesOnClosureParameters extends LintRule implements NodeLintRule {
+class AvoidTypesOnClosureParameters extends LintRule {
+  static const LintCode code = LintCode('avoid_types_on_closure_parameters',
+      'Unnecessary type annotation on a function expression parameter.',
+      correctionMessage: 'Try removing the type annotation.');
+
   AvoidTypesOnClosureParameters()
       : super(
             name: 'avoid_types_on_closure_parameters',
@@ -41,6 +44,9 @@
   List<String> get incompatibleRules => const ['always_specify_types'];
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -79,7 +85,7 @@
   @override
   void visitSimpleFormalParameter(SimpleFormalParameter node) {
     var type = node.type;
-    if (type is TypeName && type.name.name != 'dynamic') {
+    if (type is NamedType && type.name.name != 'dynamic') {
       rule.reportLint(node.type);
     }
   }
diff --git a/lib/src/rules/avoid_unnecessary_containers.dart b/lib/src/rules/avoid_unnecessary_containers.dart
index dd1dd86..1075710 100644
--- a/lib/src/rules/avoid_unnecessary_containers.dart
+++ b/lib/src/rules/avoid_unnecessary_containers.dart
@@ -10,7 +10,8 @@
 
 const _desc = r'Avoid unnecessary containers.';
 
-const _details = r'''Avoid wrapping widgets in unnecessary containers.
+const _details = r'''
+**AVOID** wrapping widgets in unnecessary containers.
 
 Wrapping a widget in `Container` with no other parameters set has no effect 
 and makes code needlessly more complex.
@@ -46,7 +47,13 @@
 ```
 ''';
 
-class AvoidUnnecessaryContainers extends LintRule implements NodeLintRule {
+class AvoidUnnecessaryContainers extends LintRule {
+  static const LintCode code = LintCode(
+      'avoid_unnecessary_containers', "Unnecessary instance of 'Container'.",
+      correctionMessage:
+          "Try removing the 'Container' (but not it's children) from the "
+          'widget tree.');
+
   AvoidUnnecessaryContainers()
       : super(
             name: 'avoid_unnecessary_containers',
@@ -55,6 +62,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/avoid_unused_constructor_parameters.dart b/lib/src/rules/avoid_unused_constructor_parameters.dart
index f5a0963..99bed07 100644
--- a/lib/src/rules/avoid_unused_constructor_parameters.dart
+++ b/lib/src/rules/avoid_unused_constructor_parameters.dart
@@ -7,12 +7,11 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../utils.dart';
+import '../util/ascii_utils.dart';
 
 const _desc = r'Avoid defining unused parameters in constructors.';
 
 const _details = r'''
-
 **AVOID** defining unused parameters in constructors.
 
 **BAD:**
@@ -32,8 +31,11 @@
 
 ''';
 
-class AvoidUnusedConstructorParameters extends LintRule
-    implements NodeLintRule {
+class AvoidUnusedConstructorParameters extends LintRule {
+  static const LintCode code = LintCode('avoid_unused_constructor_parameters',
+      "The parameter '{0}' is not used in the constructor.",
+      correctionMessage: 'Try using the parameter or removing it.');
+
   AvoidUnusedConstructorParameters()
       : super(
             name: 'avoid_unused_constructor_parameters',
@@ -42,6 +44,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -50,17 +55,17 @@
 }
 
 class _ConstructorVisitor extends RecursiveAstVisitor {
-  final LintRule rule;
   final ConstructorDeclaration element;
   final Set<FormalParameter> unusedParameters;
 
-  _ConstructorVisitor(this.rule, this.element)
+  _ConstructorVisitor(this.element)
       : unusedParameters = element.parameters.parameters.where((p) {
           var element = p.declaredElement;
           return element != null &&
               element is! FieldFormalParameterElement &&
+              element is! SuperFormalParameterElement &&
               !element.hasDeprecated &&
-              !isJustUnderscores(element.name);
+              !element.name.isJustUnderscores;
         }).toSet();
 
   @override
@@ -80,12 +85,14 @@
     if (node.redirectedConstructor != null) return;
     if (node.externalKeyword != null) return;
 
-    var _constructorVisitor = _ConstructorVisitor(rule, node);
-    node.body.visitChildren(_constructorVisitor);
+    var constructorVisitor = _ConstructorVisitor(node);
+    node.body.visitChildren(constructorVisitor);
     for (var i in node.initializers) {
-      i.visitChildren(_constructorVisitor);
+      i.visitChildren(constructorVisitor);
     }
 
-    _constructorVisitor.unusedParameters.forEach(rule.reportLint);
+    for (var parameter in constructorVisitor.unusedParameters) {
+      rule.reportLint(parameter, arguments: [parameter.name!.lexeme]);
+    }
   }
 }
diff --git a/lib/src/rules/avoid_void_async.dart b/lib/src/rules/avoid_void_async.dart
index f01bf9a..fe4d172 100644
--- a/lib/src/rules/avoid_void_async.dart
+++ b/lib/src/rules/avoid_void_async.dart
@@ -11,11 +11,10 @@
 const _desc = r'Avoid async functions that return void.';
 
 const _details = r'''
-
-**DO** mark async functions to return Future<void>.
+**DO** mark async functions as returning Future<void>.
 
 When declaring an async method or function which does not return a value,
-declare that it returns Future<void> and not just void.
+declare that it returns `Future<void>` and not just `void`.
 
 **BAD:**
 ```dart
@@ -29,9 +28,26 @@
 Future<void> f2() async => null;
 ```
 
+**EXCEPTION:**
+
+An exception is made for top-level `main` functions, where the `Future`
+annotation *can* (and generally should) be dropped in favor of `void`.
+
+**GOOD:**
+```dart
+Future<void> f() async {}
+
+void main() async {
+  await f();
+}
+```
 ''';
 
-class AvoidVoidAsync extends LintRule implements NodeLintRule {
+class AvoidVoidAsync extends LintRule {
+  static const LintCode code = LintCode('avoid_void_async',
+      "The return type should be '{0}' when nothing is returned from an '{1}' function.",
+      correctionMessage: 'Try changing the return type.');
+
   AvoidVoidAsync()
       : super(
             name: 'avoid_void_async',
@@ -40,6 +56,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -55,23 +74,31 @@
 
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
-    if (_isAsync(node.declaredElement) && _isVoid(node.returnType)) {
-      rule.reportLint(node.name);
+    var arguments = _arguments(node.declaredElement);
+    if (arguments != null &&
+        _isVoid(node.returnType) &&
+        node.name.lexeme != 'main') {
+      rule.reportLintForToken(node.name, arguments: arguments);
     }
   }
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    if (_isAsync(node.declaredElement) && _isVoid(node.returnType)) {
-      rule.reportLint(node.name);
+    var arguments = _arguments(node.declaredElement);
+    if (arguments != null && _isVoid(node.returnType)) {
+      rule.reportLintForToken(node.name, arguments: arguments);
     }
   }
 
-  bool _isAsync(ExecutableElement? element) {
+  List<String>? _arguments(ExecutableElement? element) {
     if (element == null) {
-      return false;
+      return null;
+    } else if (element.isAsynchronous) {
+      return ['Future', 'async'];
+    } else if (element.isGenerator) {
+      return ['Stream', 'async*'];
     }
-    return element.isAsynchronous || element.isGenerator;
+    return null;
   }
 
   bool _isVoid(TypeAnnotation? typeAnnotation) =>
diff --git a/lib/src/rules/avoid_web_libraries_in_flutter.dart b/lib/src/rules/avoid_web_libraries_in_flutter.dart
index 6983cce..ec4ba50 100644
--- a/lib/src/rules/avoid_web_libraries_in_flutter.dart
+++ b/lib/src/rules/avoid_web_libraries_in_flutter.dart
@@ -13,7 +13,8 @@
 const _desc =
     r'Avoid using web-only libraries outside Flutter web plugin packages.';
 
-const _details = r'''Avoid using web libraries, `dart:html`, `dart:js` and 
+const _details = r'''
+**AVOID** using web libraries, `dart:html`, `dart:js` and 
 `dart:js_util` in Flutter packages that are not web plugins. These libraries are 
 not supported outside a web context; functionality that depends on them will
 fail at runtime in Flutter mobile, and their use is generally discouraged in
@@ -40,7 +41,11 @@
   return YamlMap();
 }
 
-class AvoidWebLibrariesInFlutter extends LintRule implements NodeLintRule {
+class AvoidWebLibrariesInFlutter extends LintRule {
+  static const LintCode code = LintCode('avoid_web_libraries_in_flutter',
+      "Don't use web-only libraries outside Flutter web plugin packages.",
+      correctionMessage: 'Try finding a different library for your needs.');
+
   /// Cache of most recent analysis root to parsed "hasFlutter" state.
   static final Map<String, bool> _rootHasFlutterCache = {};
 
@@ -52,6 +57,9 @@
             maturity: Maturity.stable,
             group: Group.errors);
 
+  @override
+  LintCode get lintCode => code;
+
   bool hasFlutterDep(File? pubspec) {
     if (pubspec == null) {
       return false;
diff --git a/lib/src/rules/await_only_futures.dart b/lib/src/rules/await_only_futures.dart
index 1d119d6..0dee272 100644
--- a/lib/src/rules/await_only_futures.dart
+++ b/lib/src/rules/await_only_futures.dart
@@ -6,7 +6,7 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Await only futures.';
 
@@ -34,7 +34,12 @@
 ```
 ''';
 
-class AwaitOnlyFutures extends LintRule implements NodeLintRule {
+class AwaitOnlyFutures extends LintRule {
+  static const LintCode code = LintCode('await_only_futures',
+      "Uses 'await' on an instance of '{0}', which is not a subtype of 'Future'.",
+      correctionMessage:
+          "Try removing the 'await' or changing the expression.");
+
   AwaitOnlyFutures()
       : super(
             name: 'await_only_futures',
@@ -43,6 +48,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -51,9 +59,6 @@
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  static const LintCode _errorCode = LintCode('await_only_futures',
-      "'await' applied to '{0}', which is not a 'Future'.");
-
   final LintRule rule;
 
   _Visitor(this.rule);
@@ -66,11 +71,10 @@
     if (!(type == null ||
         type.isDartAsyncFuture ||
         type.isDynamic ||
-        DartTypeUtilities.extendsClass(type, 'Future', 'dart.async') ||
-        DartTypeUtilities.implementsInterface(type, 'Future', 'dart.async') ||
-        DartTypeUtilities.isClass(type, 'FutureOr', 'dart.async'))) {
-      rule.reportLintForToken(node.awaitKeyword,
-          errorCode: _errorCode, arguments: [type]);
+        type.extendsClass('Future', 'dart.async') ||
+        type.implementsInterface('Future', 'dart.async') ||
+        type.isDartAsyncFutureOr)) {
+      rule.reportLintForToken(node.awaitKeyword, arguments: [type]);
     }
   }
 }
diff --git a/lib/src/rules/camel_case_extensions.dart b/lib/src/rules/camel_case_extensions.dart
index 2bfa519..6155812 100644
--- a/lib/src/rules/camel_case_extensions.dart
+++ b/lib/src/rules/camel_case_extensions.dart
@@ -11,7 +11,6 @@
 const _desc = r'Name extensions using UpperCamelCase.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **DO** name extensions using `UpperCamelCase`.
@@ -31,7 +30,12 @@
 ```
 ''';
 
-class CamelCaseExtensions extends LintRule implements NodeLintRule {
+class CamelCaseExtensions extends LintRule {
+  static const LintCode code = LintCode('camel_case_extensions',
+      "The extension name '{0}' isn't an UpperCamelCase identifier.",
+      correctionMessage:
+          'Try changing the name to follow the UpperCamelCase style.');
+
   CamelCaseExtensions()
       : super(
             name: 'camel_case_extensions',
@@ -40,6 +44,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -55,8 +62,8 @@
   @override
   void visitExtensionDeclaration(ExtensionDeclaration node) {
     var name = node.name;
-    if (name != null && !isCamelCase(name.name)) {
-      rule.reportLint(name);
+    if (name != null && !isCamelCase(name.lexeme)) {
+      rule.reportLintForToken(name, arguments: [name.lexeme]);
     }
   }
 }
diff --git a/lib/src/rules/camel_case_types.dart b/lib/src/rules/camel_case_types.dart
index e561540..476829e 100644
--- a/lib/src/rules/camel_case_types.dart
+++ b/lib/src/rules/camel_case_types.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
@@ -11,7 +12,6 @@
 const _desc = r'Name types using UpperCamelCase.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **DO** name types using UpperCamelCase.
@@ -34,7 +34,12 @@
 
 ''';
 
-class CamelCaseTypes extends LintRule implements NodeLintRule {
+class CamelCaseTypes extends LintRule {
+  static const LintCode code = LintCode('camel_case_types',
+      "The type name '{0}' isn't an UpperCamelCase identifier.",
+      correctionMessage:
+          'Try changing the name to follow the UpperCamelCase style.');
+
   CamelCaseTypes()
       : super(
             name: 'camel_case_types',
@@ -43,6 +48,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -50,6 +58,7 @@
     registry.addClassDeclaration(this, visitor);
     registry.addClassTypeAlias(this, visitor);
     registry.addFunctionTypeAlias(this, visitor);
+    registry.addEnumDeclaration(this, visitor);
   }
 }
 
@@ -58,9 +67,10 @@
 
   _Visitor(this.rule);
 
-  void check(SimpleIdentifier name) {
-    if (!isCamelCase(name.toString())) {
-      rule.reportLint(name);
+  void check(Token name) {
+    var lexeme = name.lexeme;
+    if (!isCamelCase(lexeme)) {
+      rule.reportLintForToken(name, arguments: [lexeme]);
     }
   }
 
@@ -75,6 +85,11 @@
   }
 
   @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    check(node.name);
+  }
+
+  @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
     check(node.name);
   }
diff --git a/lib/src/rules/cancel_subscriptions.dart b/lib/src/rules/cancel_subscriptions.dart
index 8329ff3..031f886 100644
--- a/lib/src/rules/cancel_subscriptions.dart
+++ b/lib/src/rules/cancel_subscriptions.dart
@@ -5,13 +5,12 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 import '../util/leak_detector_visitor.dart';
 
 const _desc = r'Cancel instances of dart.async.StreamSubscription.';
 
 const _details = r'''
-
 **DO** invoke `cancel` on instances of `dart.async.StreamSubscription`.
 
 Cancelling instances of StreamSubscription prevents memory leaks and unexpected
@@ -58,10 +57,15 @@
 
 ''';
 
-bool _isSubscription(DartType type) => DartTypeUtilities.implementsInterface(
-    type, 'StreamSubscription', 'dart.async');
+bool _isSubscription(DartType type) =>
+    type.implementsInterface('StreamSubscription', 'dart.async');
 
-class CancelSubscriptions extends LintRule implements NodeLintRule {
+class CancelSubscriptions extends LintRule {
+  static const LintCode code = LintCode(
+      'cancel_subscriptions', "Uncancelled instance of 'StreamSubscription'.",
+      correctionMessage: "Try invoking 'cancel' in the function in which the "
+          "'StreamSubscription' was created.");
+
   CancelSubscriptions()
       : super(
             name: 'cancel_subscriptions',
@@ -70,6 +74,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -86,5 +93,5 @@
     _isSubscription: _cancelMethodName
   };
 
-  _Visitor(LintRule rule) : super(rule);
+  _Visitor(super.rule);
 }
diff --git a/lib/src/rules/cascade_invocations.dart b/lib/src/rules/cascade_invocations.dart
index 6b9da46..8d12791 100644
--- a/lib/src/rules/cascade_invocations.dart
+++ b/lib/src/rules/cascade_invocations.dart
@@ -8,13 +8,12 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Cascade consecutive method invocations on the same reference.';
 
 const _details = r'''
-
-**DO** Use the cascading style when succesively invoking methods on the same
+**DO** Use the cascading style when successively invoking methods on the same
 reference.
 
 **BAD:**
@@ -74,8 +73,7 @@
 ExecutableElement? _getExecutableElementFromMethodInvocation(
     MethodInvocation node) {
   if (_isInvokedWithoutNullAwareOperator(node.operator)) {
-    var executableElement =
-        DartTypeUtilities.getCanonicalElementFromIdentifier(node.methodName);
+    var executableElement = node.methodName.canonicalElement;
     if (executableElement is ExecutableElement) {
       return executableElement;
     }
@@ -86,29 +84,27 @@
 Element? _getPrefixElementFromExpression(Expression rawExpression) {
   var expression = rawExpression.unParenthesized;
   if (expression is PrefixedIdentifier) {
-    return DartTypeUtilities.getCanonicalElementFromIdentifier(
-        expression.prefix);
+    return expression.prefix.canonicalElement;
   } else if (expression is PropertyAccess &&
       _isInvokedWithoutNullAwareOperator(expression.operator) &&
       expression.target is SimpleIdentifier) {
-    return DartTypeUtilities.getCanonicalElementFromIdentifier(
-        expression.target);
+    return expression.target.canonicalElement;
   }
   return null;
 }
 
 Element? _getTargetElementFromCascadeExpression(CascadeExpression node) =>
-    DartTypeUtilities.getCanonicalElementFromIdentifier(node.target);
+    node.target.canonicalElement;
 
 Element? _getTargetElementFromMethodInvocation(MethodInvocation node) =>
-    DartTypeUtilities.getCanonicalElementFromIdentifier(node.target);
+    node.target.canonicalElement;
 
 bool _isInvokedWithoutNullAwareOperator(Token? token) =>
     token?.type == TokenType.PERIOD;
 
 /// Rule to lint consecutive invocations of methods or getters on the same
 /// reference that could be done with the cascade operator.
-class CascadeInvocations extends LintRule implements NodeLintRule {
+class CascadeInvocations extends LintRule {
   /// Default constructor.
   CascadeInvocations()
       : super(
@@ -133,12 +129,33 @@
 
   /// Whether this expression can be joined with a previous expression via a
   /// cascade operation.
+  ///
+  /// If this expression is a [PropertyAccess], [CascadeExpression], or
+  /// [MethodInvocation] in which the target is not a [SimpleIdentifier], or an
+  /// [AssignmentExpression] in which the left side is not a [SimpleIdentifier],
+  /// it cannot join. See bugs https://github.com/dart-lang/linter/issues/1323
+  /// and https://github.com/dart-lang/linter/issues/3240.
+  // TODO(https://github.com/dart-lang/linter/issues/3240): Refactor this lint
+  // rule to use
+  // DartTypeUtilities.canonicalElementsFromIdentifiersAreEqual(), which
+  // should remove this need for checking for a simple target.
   final bool canJoin;
 
   /// Whether this expression can receive an additional expression with a
   /// cascade operation.
   ///
   /// For example, `a.b = 1` can receive, but `a = 1` cannot receive.
+  ///
+  /// If this expression is a [PropertyAccess], [CascadeExpression], or
+  /// [MethodInvocation] in which the target is not a [SimpleIdentifier], or an
+  /// [AssignmentExpression] in which the left side is not a [SimpleIdentifier],
+  /// it cannot receive. See bugs
+  /// https://github.com/dart-lang/linter/issues/1323 and
+  /// https://github.com/dart-lang/linter/issues/3240.
+  // TODO(https://github.com/dart-lang/linter/issues/3240): Refactor this lint
+  // rule to use
+  // DartTypeUtilities.canonicalElementsFromIdentifiersAreEqual(), which
+  // should remove this need for checking for a simple target.
   final bool canReceive;
   final bool canBeCascaded;
 
@@ -184,8 +201,7 @@
     var leftExpression = node.leftHandSide.unParenthesized;
     if (leftExpression is SimpleIdentifier) {
       return _CascadableExpression._internal(
-          DartTypeUtilities.getCanonicalElement(leftExpression.staticElement),
-          [node.rightHandSide],
+          leftExpression.staticElement?.canonicalElement, [node.rightHandSide],
           canReceive: node.operator.type != TokenType.QUESTION_QUESTION_EQ,
           isCritical: true);
     }
@@ -198,21 +214,24 @@
         canJoin: true, canReceive: canReceive, canBeCascaded: true);
   }
 
-  factory _CascadableExpression._fromCascadeExpression(
-          CascadeExpression node) =>
-      _CascadableExpression._internal(
-          _getTargetElementFromCascadeExpression(node), node.cascadeSections,
-          canJoin: true, canReceive: true, canBeCascaded: true);
+  factory _CascadableExpression._fromCascadeExpression(CascadeExpression node) {
+    var targetIsSimple = node.target is SimpleIdentifier;
+    return _CascadableExpression._internal(
+        _getTargetElementFromCascadeExpression(node), node.cascadeSections,
+        canJoin: targetIsSimple,
+        canReceive: targetIsSimple,
+        canBeCascaded: true);
+  }
 
   factory _CascadableExpression._fromMethodInvocation(MethodInvocation node) {
     var executableElement = _getExecutableElementFromMethodInvocation(node);
     var isNonStatic = executableElement?.isStatic == false;
     if (isNonStatic) {
-      var isSimpleIdentifier = node.target is SimpleIdentifier;
+      var targetIsSimple = node.target is SimpleIdentifier;
       return _CascadableExpression._internal(
           _getTargetElementFromMethodInvocation(node), [node.argumentList],
-          canJoin: isSimpleIdentifier,
-          canReceive: isSimpleIdentifier,
+          canJoin: targetIsSimple,
+          canReceive: targetIsSimple,
           canBeCascaded: true);
     }
     return nullCascadableExpression;
@@ -220,37 +239,12 @@
 
   factory _CascadableExpression._fromPrefixedIdentifier(
           PrefixedIdentifier node) =>
-      _CascadableExpression._internal(
-          DartTypeUtilities.getCanonicalElementFromIdentifier(node.prefix), [],
+      _CascadableExpression._internal(node.prefix.canonicalElement, [],
           canJoin: true, canReceive: true, canBeCascaded: true);
 
   factory _CascadableExpression._fromPropertyAccess(PropertyAccess node) {
     var targetIsSimple = node.target is SimpleIdentifier;
-    return _CascadableExpression._internal(
-        DartTypeUtilities.getCanonicalElementFromIdentifier(node.target), [],
-        // If the target is something like `(a + b).x`, then node can neither
-        // join, nor receive.
-        //
-        // This restriction is quite limiting. It means that this rule cannot
-        // report on the following code:
-        //
-        //     b1.a
-        //       ..f = 1
-        //       ..g = 1;
-        //     b2.a.f = 2; // Could report here.
-        //
-        // But it may be difficult to accurately know if complicated
-        // expressions are cascadable. Just because two expressions are "equal"
-        // or look the same, does not mean they can be cascaded; for example:
-        //
-        //     (b1 + b2).a
-        //       ..f = 1
-        //       ..g = 1;
-        //     (b1 + b2).a.f = 2; // Should not report here.
-        //
-        // TODO(srawlins): Refactor this lint rule to use
-        // DartTypeUtilities.canonicalElementsFromIdentifiersAreEqual(), which
-        // should fix this issue.
+    return _CascadableExpression._internal(node.target.canonicalElement, [],
         canJoin: targetIsSimple,
         canReceive: targetIsSimple,
         canBeCascaded: true);
@@ -273,13 +267,40 @@
       !_hasCriticalDependencies(expressionBox);
 
   bool _hasCriticalDependencies(_CascadableExpression expressionBox) {
-    bool _isCriticalNode(AstNode node) =>
-        DartTypeUtilities.getCanonicalElementFromIdentifier(node) ==
-        expressionBox.element;
-    return expressionBox.isCritical &&
-        criticalNodes.any((node) =>
-            _isCriticalNode(node) ||
-            DartTypeUtilities.traverseNodesInDFS(node).any(_isCriticalNode));
+    if (!expressionBox.isCritical) return false;
+
+    for (var node in criticalNodes) {
+      if (_NodeVisitor(expressionBox).isOrHasCriticalNode(node)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
+
+class _NodeVisitor extends UnifyingAstVisitor {
+  final _CascadableExpression expressionBox;
+
+  bool foundCriticalNode = false;
+  _NodeVisitor(this.expressionBox);
+
+  bool isCriticalNode(AstNode node) =>
+      node.canonicalElement == expressionBox.element;
+
+  bool isOrHasCriticalNode(AstNode node) {
+    node.accept(this);
+    return foundCriticalNode;
+  }
+
+  @override
+  visitNode(AstNode node) {
+    if (foundCriticalNode) return;
+    foundCriticalNode = isCriticalNode(node);
+
+    if (!foundCriticalNode) {
+      super.visitNode(node);
+    }
   }
 }
 
diff --git a/lib/src/rules/cast_nullable_to_non_nullable.dart b/lib/src/rules/cast_nullable_to_non_nullable.dart
index 507e43b..860e229 100644
--- a/lib/src/rules/cast_nullable_to_non_nullable.dart
+++ b/lib/src/rules/cast_nullable_to_non_nullable.dart
@@ -11,8 +11,7 @@
 const _desc = r"Don't cast a nullable value to a non nullable type.";
 
 const _details = r'''
-
-Don't cast a nullable value to a non nullable type. This hides a null check
+**DON'T** cast a nullable value to a non nullable type. This hides a null check
 and most of the time it is not what is expected.
 
 **BAD:**
@@ -37,7 +36,7 @@
 
 ''';
 
-class CastNullableToNonNullable extends LintRule implements NodeLintRule {
+class CastNullableToNonNullable extends LintRule {
   CastNullableToNonNullable()
       : super(
           name: 'cast_nullable_to_non_nullable',
diff --git a/lib/src/rules/close_sinks.dart b/lib/src/rules/close_sinks.dart
index 3991188..b71b2d1 100644
--- a/lib/src/rules/close_sinks.dart
+++ b/lib/src/rules/close_sinks.dart
@@ -5,13 +5,12 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 import '../util/leak_detector_visitor.dart';
 
 const _desc = r'Close instances of `dart.core.Sink`.';
 
 const _details = r'''
-
 **DO** invoke `close` on instances of `dart.core.Sink`.
 
 Closing instances of Sink prevents memory leaks and unexpected behavior.
@@ -57,13 +56,17 @@
 
 ''';
 
-bool _isSink(DartType type) =>
-    DartTypeUtilities.implementsInterface(type, 'Sink', 'dart.core');
+bool _isSink(DartType type) => type.implementsInterface('Sink', 'dart.core');
 
-bool _isSocket(DartType type) =>
-    DartTypeUtilities.implementsInterface(type, 'Socket', 'dart.io');
+bool _isSocket(DartType type) => type.implementsInterface('Socket', 'dart.io');
 
-class CloseSinks extends LintRule implements NodeLintRule {
+class CloseSinks extends LintRule {
+  static const LintCode code = LintCode(
+      'close_sinks', "Unclosed instance of 'Sink'.",
+      correctionMessage:
+          "Try invoking 'close' in the function in which the 'Sink' was "
+          'created.');
+
   CloseSinks()
       : super(
             name: 'close_sinks',
@@ -72,6 +75,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -90,5 +96,5 @@
     _isSocket: _destroyMethodName
   };
 
-  _Visitor(LintRule rule) : super(rule);
+  _Visitor(super.rule);
 }
diff --git a/lib/src/rules/collection_methods_unrelated_type.dart b/lib/src/rules/collection_methods_unrelated_type.dart
new file mode 100644
index 0000000..d424499
--- /dev/null
+++ b/lib/src/rules/collection_methods_unrelated_type.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../analyzer.dart';
+import '../util/unrelated_types_visitor.dart';
+
+const _desc = 'Invocation of various collection methods with arguments of '
+    'unrelated types.';
+
+const _details = r'''
+**DON'T** invoke certain collection method with an argument with an unrelated
+type.
+
+Doing this will invoke `==` on the collection's elements and most likely will
+return `false`.
+
+An argument passed to a collection method should relate to the collection type
+as follows:
+
+* an argument to `Iterable<E>.contains` should be related to `E`
+* an argument to `List<E>.remove` should be related to `E`
+* an argument to `Map<K, V>.containsKey` should be related to `K`
+* an argument to `Map<K, V>.containsValue` should be related to `V`
+* an argument to `Map<K, V>.remove` should be related to `K`
+* an argument to `Map<K, V>.[]` should be related to `K`
+* an argument to `Queue<E>.remove` should be related to `E`
+* an argument to `Set<E>.lookup` should be related to `E`
+* an argument to `Set<E>.remove` should be related to `E`
+
+**BAD:**
+```dart
+void someFunction() {
+  var list = <int>[];
+  if (list.contains('1')) print('someFunction'); // LINT
+}
+```
+
+**BAD:**
+```dart
+void someFunction() {
+  var set = <int>{};
+  set.remove('1'); // LINT
+}
+```
+
+**GOOD:**
+```dart
+void someFunction() {
+  var list = <int>[];
+  if (list.contains(1)) print('someFunction'); // OK
+}
+```
+
+**GOOD:**
+```dart
+void someFunction() {
+  var set = <int>{};
+  set.remove(1); // OK
+}
+```
+
+''';
+
+class CollectionMethodsUnrelatedType extends LintRule {
+  static const LintCode code = LintCode('collection_methods_unrelated_type',
+      "The argument type '{0}' isn't related to '{1}'.");
+
+  CollectionMethodsUnrelatedType()
+      : super(
+            name: 'collection_methods_unrelated_type',
+            description: _desc,
+            details: _details,
+            group: Group.errors);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this, context.typeSystem, context.typeProvider);
+    registry.addIndexExpression(this, visitor);
+    registry.addMethodInvocation(this, visitor);
+  }
+}
+
+class _Visitor extends UnrelatedTypesProcessors {
+  _Visitor(super.rule, super.typeSystem, super.typeProvider);
+
+  @override
+  List<MethodDefinition> get methods => [
+        // Argument to `Iterable<E>.contains` should be assignable to `E`.
+        MethodDefinitionForElement(
+          typeProvider.iterableElement,
+          'contains',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+        // Argument to `List<E>.remove` should be assignable to `E`.
+        MethodDefinitionForElement(
+          typeProvider.listElement,
+          'remove',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+        // Argument to `Map<K, V>.containsKey` should be assignable to `K`.
+        MethodDefinitionForElement(
+          typeProvider.mapElement,
+          'containsKey',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+        // Argument to `Map<K, V>.containsValue` should be assignable to `V`.
+        MethodDefinitionForElement(
+          typeProvider.mapElement,
+          'containsValue',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+          typeArgumentIndex: 1,
+        ),
+        // Argument to `Map<K, V>.remove` should be assignable to `K`.
+        MethodDefinitionForElement(
+          typeProvider.mapElement,
+          'remove',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+        // Argument to `Queue<E>.remove` should be assignable to `E`.
+        MethodDefinitionForName(
+          'dart.collection',
+          'Queue',
+          'remove',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+        // Argument to `Set<E>.lookup` should be assignable to `E`.
+        MethodDefinitionForElement(
+          typeProvider.setElement,
+          'lookup',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+        // Argument to `Set<E>.remove` should be assignable to `E`.
+        MethodDefinitionForElement(
+          typeProvider.setElement,
+          'remove',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+      ];
+
+  @override
+  List<MethodDefinition> get indexOperators => [
+        // Argument to `Map<K, V>.[]` should be assignable to `K`.
+        MethodDefinitionForElement(
+          typeProvider.mapElement,
+          '[]',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+      ];
+}
diff --git a/lib/src/rules/combinators_ordering.dart b/lib/src/rules/combinators_ordering.dart
new file mode 100644
index 0000000..83d166b
--- /dev/null
+++ b/lib/src/rules/combinators_ordering.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:collection/collection.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Sort combinator names alphabetically.';
+
+const _details = r'''
+**DO** sort combinator names alphabetically.
+
+**BAD:**
+```dart
+import 'a.dart' show B, A hide D, C;
+export 'a.dart' show B, A hide D, C;
+```
+
+**GOOD:**
+```dart
+import 'a.dart' show A, B hide C, D;
+export 'a.dart' show A, B hide C, D;
+```
+
+''';
+
+class CombinatorsOrdering extends LintRule {
+  CombinatorsOrdering()
+      : super(
+          name: 'combinators_ordering',
+          description: _desc,
+          details: _details,
+          group: Group.style,
+        );
+
+  @override
+  void registerNodeProcessors(
+    NodeLintRegistry registry,
+    LinterContext context,
+  ) {
+    var visitor = _Visitor(this);
+    registry.addHideCombinator(this, visitor);
+    registry.addShowCombinator(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  _Visitor(this.rule);
+
+  final LintRule rule;
+
+  @override
+  void visitHideCombinator(HideCombinator node) {
+    if (!node.hiddenNames.map((e) => e.name).isSorted()) {
+      rule.reportLint(node);
+    }
+  }
+
+  @override
+  void visitShowCombinator(ShowCombinator node) {
+    if (!node.shownNames.map((e) => e.name).isSorted()) {
+      rule.reportLint(node);
+    }
+  }
+}
diff --git a/lib/src/rules/comment_references.dart b/lib/src/rules/comment_references.dart
index a530181..2a0bb97 100644
--- a/lib/src/rules/comment_references.dart
+++ b/lib/src/rules/comment_references.dart
@@ -10,23 +10,14 @@
 const _desc = r'Only reference in scope identifiers in doc comments.';
 
 const _details = r'''
-
 **DO** reference only in scope identifiers in doc comments.
 
 If you surround things like variable, method, or type names in square brackets,
-then [dartdoc](https://dart.dev/guides/language/effective-dart/documentation) will look
+then [`dart doc`](https://dart.dev/tools/dart-doc) will look
 up the name and link to its docs.  For this all to work, ensure that all
 identifiers in docs wrapped in brackets are in scope.
 
-For example,
-
-**GOOD:**
-```dart
-/// Return the larger of [a] or [b].
-int max_int(int a, int b) { ... }
-```
-
-On the other hand, assuming `outOfScopeId` is out of scope:
+For example, assuming `outOfScopeId` is out of scope:
 
 **BAD:**
 ```dart
@@ -34,9 +25,15 @@
 bool isOutOfRange(int value) { ... }
 ```
 
-Note that the square bracket comment format is designed to allow 
-comments to refer to declarations using a fairly natural format 
-but does not allow *arbitrary expressions*.  In particular, code 
+**GOOD:**
+```dart
+/// Return the larger of [a] or [b].
+int max_int(int a, int b) { ... }
+```
+
+Note that the square bracket comment format is designed to allow
+comments to refer to declarations using a fairly natural format
+but does not allow *arbitrary expressions*.  In particular, code
 references within square brackets can consist of either
 
 - a single identifier where the identifier is any identifier in scope for the comment (see the spec for what is in scope in doc comments),
@@ -46,7 +43,11 @@
 
 ''';
 
-class CommentReferences extends LintRule implements NodeLintRule {
+class CommentReferences extends LintRule {
+  static const LintCode code = LintCode(
+      'comment_references', "The referenced name isn't visible in scope.",
+      correctionMessage: 'Try adding an import for the referenced name.');
+
   CommentReferences()
       : super(
             name: 'comment_references',
@@ -55,6 +56,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -65,11 +69,15 @@
 
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
+  final links = <String>[];
 
   _Visitor(this.rule);
 
   @override
   void visitComment(Comment node) {
+    // clear links of previous comments
+    links.clear();
+
     // Check for keywords that are not treated as references by the parser
     // but should be flagged by the linter.
     // Note that no special care is taken to handle embedded code blocks.
@@ -86,6 +94,10 @@
               rule.reporter.reportErrorForOffset(
                   rule.lintCode, nameOffset, reference.length);
             }
+            if (rightIndex + 1 < comment.length &&
+                comment[rightIndex + 1] == ':') {
+              links.add(reference);
+            }
           }
           leftIndex = rightIndex < 0 ? -1 : comment.indexOf('[', rightIndex);
         }
@@ -95,9 +107,12 @@
 
   @override
   void visitCommentReference(CommentReference node) {
-    var identifier = node.identifier;
-    if (!identifier.isSynthetic && identifier.staticElement == null) {
-      rule.reportLint(identifier);
+    var expression = node.expression;
+    if (expression.isSynthetic) return;
+    if (expression is Identifier &&
+        expression.staticElement == null &&
+        !links.contains(expression.name)) {
+      rule.reportLint(expression);
     }
   }
 
diff --git a/lib/src/rules/conditional_uri_does_not_exist.dart b/lib/src/rules/conditional_uri_does_not_exist.dart
new file mode 100644
index 0000000..ea64e81
--- /dev/null
+++ b/lib/src/rules/conditional_uri_does_not_exist.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Missing conditional import.';
+
+const _details = r'''
+**DON'T** reference files that do not exist in conditional imports.
+
+Code may fail at runtime if the condition evaluates such that the missing file
+needs to be imported.
+
+**BAD:**
+```dart
+import 'file_that_does_exist.dart'
+  if (condition) 'file_that_does_not_exist.dart';
+```
+
+**GOOD:**
+```dart
+import 'file_that_does_exist.dart'
+  if (condition) 'file_that_also_does_exist.dart';
+```
+
+''';
+
+class ConditionalUriDoesNotExist extends LintRule {
+  ConditionalUriDoesNotExist()
+      : super(
+            name: 'conditional_uri_does_not_exist',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addConfiguration(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  static const LintCode code = LintCode('conditional_uri_does_not_exist',
+      "The target of the conditional URI '{0}' doesn't exist.",
+      correctionMessage: 'Try creating the file referenced by the URI, or '
+          'try using a URI for a file that does exist.');
+
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitConfiguration(Configuration configuration) {
+    var uri = configuration.resolvedUri;
+    if (uri is DirectiveUriWithRelativeUriString) {
+      var source = uri is DirectiveUriWithSource ? uri.source : null;
+      // Checking source with .exists() will not detect the presence of overlays
+      // in the analysis server (although running the script when the files
+      // don't exist on disk would also fail to find it).
+      if (!(source?.exists() ?? false)) {
+        rule.reportLint(configuration.uri,
+            arguments: [uri.relativeUriString], errorCode: code);
+      }
+    }
+  }
+}
diff --git a/lib/src/rules/constant_identifier_names.dart b/lib/src/rules/constant_identifier_names.dart
index d41b899..6dc7570 100644
--- a/lib/src/rules/constant_identifier_names.dart
+++ b/lib/src/rules/constant_identifier_names.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
@@ -11,7 +12,6 @@
 const _desc = r'Prefer using lowerCamelCase for constant names.';
 
 const _details = r'''
-
 **PREFER** using lowerCamelCase for constant names.
 
 In new code, use `lowerCamelCase` for constant variables, including enum values.
@@ -19,6 +19,17 @@
 In existing code that uses `ALL_CAPS_WITH_UNDERSCORES` for constants, you may
 continue to use all caps to stay consistent.
 
+**BAD:**
+```dart
+const PI = 3.14;
+const kDefaultTimeout = 1000;
+final URL_SCHEME = RegExp('^([a-z]+):');
+
+class Dice {
+  static final NUMBER_GENERATOR = Random();
+}
+```
+
 **GOOD:**
 ```dart
 const pi = 3.14;
@@ -30,21 +41,14 @@
 }
 ```
 
-**BAD:**
-```dart
-const PI = 3.14;
-const kDefaultTimeout = 1000;
-final URL_SCHEME = RegExp('^([a-z]+):');
-
-class Dice {
-  static final NUMBER_GENERATOR = Random();
-}
-
-```
-
 ''';
 
-class ConstantIdentifierNames extends LintRule implements NodeLintRule {
+class ConstantIdentifierNames extends LintRule {
+  static const LintCode code = LintCode('constant_identifier_names',
+      "The constant name '{0}' isn't a lowerCamelCase identifier.",
+      correctionMessage:
+          'Try changing the name to follow the lowerCamelCase style.');
+
   ConstantIdentifierNames()
       : super(
             name: 'constant_identifier_names',
@@ -53,6 +57,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -67,9 +74,10 @@
 
   _Visitor(this.rule);
 
-  void checkIdentifier(SimpleIdentifier id) {
-    if (!isLowerCamelCase(id.name)) {
-      rule.reportLint(id);
+  void checkIdentifier(Token id) {
+    var name = id.lexeme;
+    if (!isLowerCamelCase(name)) {
+      rule.reportLintForToken(id, arguments: [name]);
     }
   }
 
diff --git a/lib/src/rules/control_flow_in_finally.dart b/lib/src/rules/control_flow_in_finally.dart
index e7de4f2..450d48b 100755
--- a/lib/src/rules/control_flow_in_finally.dart
+++ b/lib/src/rules/control_flow_in_finally.dart
@@ -10,27 +10,11 @@
 const _desc = r'Avoid control flow in finally blocks.';
 
 const _details = r'''
-
 **AVOID** control flow leaving finally blocks.
 
 Using control flow in finally blocks will inevitably cause unexpected behavior
 that is hard to debug.
 
-**GOOD:**
-```dart
-class Ok {
-  double compliantMethod() {
-    var i = 5;
-    try {
-      i = 1 / 0;
-    } catch (e) {
-      print(e); // OK
-    }
-    return i;
-  }
-}
-```
-
 **BAD:**
 ```dart
 class BadReturn {
@@ -82,9 +66,28 @@
 }
 ```
 
+**GOOD:**
+```dart
+class Ok {
+  double compliantMethod() {
+    var i = 5;
+    try {
+      i = 1 / 0;
+    } catch (e) {
+      print(e); // OK
+    }
+    return i;
+  }
+}
+```
+
 ''';
 
-class ControlFlowInFinally extends LintRule implements NodeLintRule {
+class ControlFlowInFinally extends LintRule {
+  static const LintCode code = LintCode(
+      'control_flow_in_finally', "Use of '{0}' in a 'finally' clause.",
+      correctionMessage: 'Try restructuring the code.');
+
   ControlFlowInFinally()
       : super(
             name: 'control_flow_in_finally',
@@ -93,6 +96,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -109,7 +115,8 @@
 abstract class ControlFlowInFinallyBlockReporterMixin {
   LintRule get rule;
 
-  void reportIfFinallyAncestorExists(AstNode node, {AstNode? ancestor}) {
+  void reportIfFinallyAncestorExists(AstNode node,
+      {required String kind, AstNode? ancestor}) {
     var tryStatement = node.thisOrAncestorOfType<TryStatement>();
     var finallyBlock = tryStatement?.finallyBlock;
     bool finallyBlockAncestorPredicate(AstNode n) => n == finallyBlock;
@@ -122,7 +129,7 @@
     var enablerNode = _findEnablerNode(
         ancestor, finallyBlockAncestorPredicate, node, tryStatement);
     if (enablerNode == null) {
-      rule.reportLint(node);
+      rule.reportLint(node, arguments: [kind]);
     }
   }
 
@@ -154,16 +161,17 @@
 
   @override
   void visitBreakStatement(BreakStatement node) {
-    reportIfFinallyAncestorExists(node, ancestor: node.target);
+    reportIfFinallyAncestorExists(node, ancestor: node.target, kind: 'break');
   }
 
   @override
   void visitContinueStatement(ContinueStatement node) {
-    reportIfFinallyAncestorExists(node, ancestor: node.target);
+    reportIfFinallyAncestorExists(node,
+        ancestor: node.target, kind: 'continue');
   }
 
   @override
   void visitReturnStatement(ReturnStatement node) {
-    reportIfFinallyAncestorExists(node);
+    reportIfFinallyAncestorExists(node, kind: 'return');
   }
 }
diff --git a/lib/src/rules/curly_braces_in_flow_control_structures.dart b/lib/src/rules/curly_braces_in_flow_control_structures.dart
index 8e40160..27da8e0 100644
--- a/lib/src/rules/curly_braces_in_flow_control_structures.dart
+++ b/lib/src/rules/curly_braces_in_flow_control_structures.dart
@@ -10,12 +10,17 @@
 const _desc = r'DO use curly braces for all flow control structures.';
 
 const _details = r'''
-
 **DO** use curly braces for all flow control structures.
 
 Doing so avoids the [dangling else](https://en.wikipedia.org/wiki/Dangling_else)
 problem.
 
+**BAD:**
+```dart
+if (overflowChars != other.overflowChars)
+  return overflowChars < other.overflowChars;
+```
+
 **GOOD:**
 ```dart
 if (isWeekDay) {
@@ -43,15 +48,14 @@
 }
 ```
 
-**BAD:**
-```dart
-if (overflowChars != other.overflowChars)
-  return overflowChars < other.overflowChars;
-```
 ''';
 
-class CurlyBracesInFlowControlStructures extends LintRule
-    implements NodeLintRule {
+class CurlyBracesInFlowControlStructures extends LintRule {
+  static const LintCode code = LintCode(
+      'curly_braces_in_flow_control_structures',
+      'Statements in {0} should be enclosed in a block.',
+      correctionMessage: 'Try wrapping the statement in a block.');
+
   CurlyBracesInFlowControlStructures()
       : super(
             name: 'curly_braces_in_flow_control_structures',
@@ -60,6 +64,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -77,12 +84,12 @@
 
   @override
   void visitDoStatement(DoStatement node) {
-    _check(node.body);
+    _check('a do', node.body);
   }
 
   @override
   void visitForStatement(ForStatement node) {
-    _check(node.body);
+    _check('a for', node.body);
   }
 
   @override
@@ -91,32 +98,31 @@
     if (elseStatement == null) {
       var parent = node.parent;
       if (parent is IfStatement && node == parent.elseStatement) {
-        _check(node.thenStatement);
+        _check('an if', node.thenStatement);
         return;
       }
       if (node.thenStatement is Block) return;
 
       var unit = node.root as CompilationUnit;
       var lineInfo = unit.lineInfo;
-      if (lineInfo != null &&
-          lineInfo.getLocation(node.rightParenthesis.end).lineNumber !=
-              lineInfo.getLocation(node.thenStatement.end).lineNumber) {
-        rule.reportLint(node.thenStatement);
+      if (lineInfo.getLocation(node.rightParenthesis.end).lineNumber !=
+          lineInfo.getLocation(node.thenStatement.end).lineNumber) {
+        rule.reportLint(node.thenStatement, arguments: ['an if']);
       }
     } else {
-      _check(node.thenStatement);
+      _check('an if', node.thenStatement);
       if (elseStatement is! IfStatement) {
-        _check(elseStatement);
+        _check('an if', elseStatement);
       }
     }
   }
 
   @override
   void visitWhileStatement(WhileStatement node) {
-    _check(node.body);
+    _check('a while', node.body);
   }
 
-  void _check(Statement node) {
-    if (node is! Block) rule.reportLint(node);
+  void _check(String where, Statement node) {
+    if (node is! Block) rule.reportLint(node, arguments: [where]);
   }
 }
diff --git a/lib/src/rules/dangling_library_doc_comments.dart b/lib/src/rules/dangling_library_doc_comments.dart
new file mode 100644
index 0000000..c0e282e
--- /dev/null
+++ b/lib/src/rules/dangling_library_doc_comments.dart
@@ -0,0 +1,173 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Attach library doc comments to library directives.';
+
+const _details = r'''
+Attach library doc comments (with `///`) to library directives, rather than
+leaving them dangling near the top of a library.
+
+**BAD:**
+```dart
+/// This is a great library.
+import 'package:math';
+```
+
+```dart
+/// This is a great library.
+
+class C {}
+```
+
+**GOOD:**
+```dart
+/// This is a great library.
+library;
+
+import 'package:math';
+
+class C {}
+```
+
+**NOTE:** An unnamed library, like `library;` above, is only supported in Dart
+2.19 and later. Code which might run in earlier versions of Dart will need to
+provide a name in the `library` directive.
+''';
+
+class DanglingLibraryDocComments extends LintRule {
+  static const LintCode code = LintCode(
+      'dangling_library_doc_comments', 'Dangling library doc comment.',
+      correctionMessage:
+          "Add a 'library' directive after the library comment.");
+
+  DanglingLibraryDocComments()
+      : super(
+            name: 'dangling_library_doc_comments',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addCompilationUnit(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final DanglingLibraryDocComments rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitCompilationUnit(CompilationUnit node) {
+    if (node.directives.isNotEmpty) {
+      // Only consider a doc comment on the first directive. Doc comments on
+      // other directives do not have the appearance of documenting the library.
+      var firstDirective = node.directives.first;
+      if (firstDirective is LibraryDirective) {
+        // Given the presense of library directive, don't worry about later doc
+        // comments in the library.
+        return;
+      }
+      if (firstDirective is PartOfDirective) {
+        // Don't worry about possible "library doc comments" in a part.
+        return;
+      }
+
+      var docComment = firstDirective.documentationComment;
+      if (docComment != null) {
+        rule.reportLintForToken(docComment.beginToken);
+        return;
+      }
+
+      return;
+    }
+
+    if (node.declarations.isEmpty) {
+      // Without any declarations, we only need to check for a doc comment as
+      // the last thing in a file.
+      Token? endComment = node.endToken.precedingComments;
+      while (endComment is CommentToken) {
+        if (endComment is DocumentationCommentToken) {
+          rule.reportLintForToken(endComment);
+        }
+        endComment = endComment.next;
+      }
+      return;
+    }
+
+    var firstDeclaration = node.declarations.first;
+    var docComment = firstDeclaration.documentationComment;
+    if (docComment == null) {
+      return;
+    }
+    var lineInfo = node.lineInfo;
+
+    if (docComment.tokens.length > 1) {
+      for (var i = 0; i < docComment.tokens.length - 1; i++) {
+        var commentToken = docComment.tokens[i];
+        var followingCommentToken = docComment.tokens[i + 1];
+        var commentEndLine = lineInfo.getLocation(commentToken.end).lineNumber;
+        var followingCommentLine =
+            lineInfo.getLocation(followingCommentToken.offset).lineNumber;
+        if (followingCommentLine > commentEndLine + 1) {
+          // There is a blank line within the declaration's doc comments.
+          rule.reportLintForToken(commentToken);
+          return;
+        }
+      }
+    }
+
+    // We must walk through the comments following the doc comment, tracking
+    // pairs of consecutive comments so as to check whether any two are
+    // separated by a blank line.
+    var commentToken = docComment.endToken;
+    var followingCommentToken = commentToken.next;
+    while (followingCommentToken != null) {
+      // Any blank line between the doc comment and following comments makes
+      // the doc comment look dangling.
+      var commentEndLine = lineInfo.getLocation(commentToken.end).lineNumber;
+      var followingCommentLine =
+          lineInfo.getLocation(followingCommentToken.offset).lineNumber;
+      if (followingCommentLine > commentEndLine + 1) {
+        // There is a blank line between the declaration's doc comment and the
+        // declaration.
+        rule.reportLint(docComment);
+        return;
+      }
+
+      commentToken = followingCommentToken;
+      followingCommentToken = followingCommentToken.next;
+    }
+
+    var commentEndLine = lineInfo.getLocation(commentToken.end).lineNumber;
+    // The syntactic entity to which a comment is "attached" is the
+    // [Comment]'s `parent`, not it's `endToken`'s `next` [Token].
+    var tokenAfterDocComment =
+        (docComment.endToken as DocumentationCommentToken).parent;
+    if (tokenAfterDocComment == null) {
+      // We shouldn't get here as the doc comment is attached to
+      // [firstDeclaration].
+      return;
+    }
+    var declarationStartLine =
+        lineInfo.getLocation(tokenAfterDocComment.offset).lineNumber;
+    if (declarationStartLine > commentEndLine + 1) {
+      // There is a blank line between the declaration's doc comment and the
+      // declaration.
+      rule.reportLint(docComment);
+    }
+  }
+}
diff --git a/lib/src/rules/depend_on_referenced_packages.dart b/lib/src/rules/depend_on_referenced_packages.dart
index 844a2be..ed9b21c 100644
--- a/lib/src/rules/depend_on_referenced_packages.dart
+++ b/lib/src/rules/depend_on_referenced_packages.dart
@@ -11,8 +11,7 @@
 const _desc = r'Depend on referenced packages.';
 
 const _details = r'''
-
-**DO** Depend on referenced packages.
+**DO** depend on referenced packages.
 
 When importing a package, add a dependency on it to your pubspec.
 
@@ -45,7 +44,12 @@
 
 ''';
 
-class DependOnReferencedPackages extends LintRule implements NodeLintRule {
+class DependOnReferencedPackages extends LintRule {
+  static const LintCode code = LintCode('depend_on_referenced_packages',
+      "The imported package '{0}' isn't a dependency of the importing package.",
+      correctionMessage:
+          "Try adding a dependency for '{0}' in the 'pubspec.yaml' file.");
+
   DependOnReferencedPackages()
       : super(
             name: 'depend_on_referenced_packages',
@@ -54,6 +58,9 @@
             group: Group.pub);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     // Only lint if we have a pubspec.
@@ -84,14 +91,27 @@
 }
 
 class _Visitor extends SimpleAstVisitor {
+  /// Virtual packages will not have explicit dependencies
+  /// and get skipped.
+  static const virtualPackages = [
+    //https://github.com/dart-lang/linter/issues/3308
+    'flutter_gen',
+  ];
+
   final DependOnReferencedPackages rule;
   final List<String> availableDeps;
 
   _Visitor(this.rule, this.availableDeps);
 
+  @override
+  void visitExportDirective(ExportDirective node) => _checkDirective(node);
+
+  @override
+  void visitImportDirective(ImportDirective node) => _checkDirective(node);
+
   void _checkDirective(UriBasedDirective node) {
     // Is it a package: uri?
-    var uriContent = node.uriContent;
+    var uriContent = node.uri.stringValue;
     if (uriContent == null) return;
     if (!uriContent.startsWith('package:')) return;
 
@@ -100,13 +120,8 @@
     if (firstSlash == -1) return;
 
     var packageName = uriContent.substring(8, firstSlash);
+    if (virtualPackages.contains(packageName)) return;
     if (availableDeps.contains(packageName)) return;
-    rule.reportLint(node.uri);
+    rule.reportLint(node.uri, arguments: [packageName]);
   }
-
-  @override
-  void visitImportDirective(ImportDirective node) => _checkDirective(node);
-
-  @override
-  void visitExportDirective(ExportDirective node) => _checkDirective(node);
 }
diff --git a/lib/src/rules/deprecated_consistency.dart b/lib/src/rules/deprecated_consistency.dart
index 14cfcb7..744ee95 100644
--- a/lib/src/rules/deprecated_consistency.dart
+++ b/lib/src/rules/deprecated_consistency.dart
@@ -11,8 +11,7 @@
 const _desc = r'Missing deprecated annotation.';
 
 const _details = r'''
-
-Do apply `@Deprecated()` consistently:
+**DO** apply `@Deprecated()` consistently:
 
 - if a class is deprecated, its constructors should also be deprecated.
 - if a field is deprecated, the constructor parameter pointing to it should also be deprecated.
@@ -49,7 +48,7 @@
 
 ''';
 
-class DeprecatedConsistency extends LintRule implements NodeLintRule {
+class DeprecatedConsistency extends LintRule {
   DeprecatedConsistency()
       : super(
           name: 'deprecated_consistency',
diff --git a/lib/src/rules/diagnostic_describe_all_properties.dart b/lib/src/rules/diagnostic_describe_all_properties.dart
index 78214ba..5717b76 100644
--- a/lib/src/rules/diagnostic_describe_all_properties.dart
+++ b/lib/src/rules/diagnostic_describe_all_properties.dart
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
 import '../ast.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 import '../util/flutter_utils.dart';
 
 const _desc = r'DO reference all public properties in debug methods.';
@@ -65,8 +66,7 @@
 ```
 ''';
 
-class DiagnosticsDescribeAllProperties extends LintRule
-    implements NodeLintRule {
+class DiagnosticsDescribeAllProperties extends LintRule {
   DiagnosticsDescribeAllProperties()
       : super(
           name: 'diagnostic_describe_all_properties',
@@ -84,51 +84,54 @@
   }
 }
 
+class _IdentifierVisitor extends RecursiveAstVisitor {
+  final List<Token> properties;
+  _IdentifierVisitor(this.properties);
+
+  @override
+  visitSimpleIdentifier(SimpleIdentifier node) {
+    String debugName;
+    String name;
+    const debugPrefix = 'debug';
+    if (node.name.startsWith(debugPrefix) &&
+        node.name.length > debugPrefix.length) {
+      debugName = node.name;
+      name = '${node.name[debugPrefix.length].toLowerCase()}'
+          '${node.name.substring(debugPrefix.length + 1)}';
+    } else {
+      name = node.name;
+      debugName =
+          '$debugPrefix${node.name[0].toUpperCase()}${node.name.substring(1)}';
+    }
+    properties.removeWhere(
+        (property) => property.lexeme == debugName || property.lexeme == name);
+
+    super.visitSimpleIdentifier(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor {
   final LintRule rule;
   final LinterContext context;
 
   _Visitor(this.rule, this.context);
 
-  void removeReferences(
-      MethodDeclaration? method, List<SimpleIdentifier> properties) {
-    if (method == null) {
-      return;
-    }
-    DartTypeUtilities.traverseNodesInDFS(method.body)
-        .whereType<SimpleIdentifier>()
-        .forEach((p) {
-      String debugName;
-      String name;
-      const debugPrefix = 'debug';
-      if (p.name.startsWith(debugPrefix) &&
-          p.name.length > debugPrefix.length) {
-        debugName = p.name;
-        name =
-            '${p.name[debugPrefix.length].toLowerCase()}${p.name.substring(debugPrefix.length + 1)}';
-      } else {
-        name = p.name;
-        debugName =
-            '$debugPrefix${p.name[0].toUpperCase()}${p.name.substring(1)}';
-      }
-      properties.removeWhere(
-          (property) => property.name == debugName || property.name == name);
-    });
+  void removeReferences(MethodDeclaration? method, List<Token> properties) {
+    method?.body.accept(_IdentifierVisitor(properties));
   }
 
-  bool skipForDiagnostic(
-          {Element? element, DartType? type, SimpleIdentifier? name}) =>
+  bool skipForDiagnostic({Element? element, DartType? type, Token? name}) =>
       isPrivate(name) || _isOverridingMember(element) || isWidgetProperty(type);
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
     // We only care about Diagnosticables.
     var type = node.declaredElement?.thisType;
-    if (!DartTypeUtilities.implementsInterface(type, 'Diagnosticable', '')) {
+    if (!type.implementsInterface('Diagnosticable', '')) {
       return;
     }
 
-    var properties = <SimpleIdentifier>[];
+    var properties = <Token>[];
     for (var member in node.members) {
       if (member is MethodDeclaration && member.isGetter) {
         if (!member.isStatic &&
@@ -159,8 +162,8 @@
       return;
     }
 
-    var debugFillProperties = node.getMethod('debugFillProperties');
-    var debugDescribeChildren = node.getMethod('debugDescribeChildren');
+    var debugFillProperties = node.members.getMethod('debugFillProperties');
+    var debugDescribeChildren = node.members.getMethod('debugDescribeChildren');
 
     // Remove any defined in debugFillProperties.
     removeReferences(debugFillProperties, properties);
@@ -169,7 +172,7 @@
     removeReferences(debugDescribeChildren, properties);
 
     // Flag the rest.
-    properties.forEach(rule.reportLint);
+    properties.forEach(rule.reportLintForToken);
   }
 
   bool _isOverridingMember(Element? member) {
diff --git a/lib/src/rules/directives_ordering.dart b/lib/src/rules/directives_ordering.dart
index 38eb966..360327f 100644
--- a/lib/src/rules/directives_ordering.dart
+++ b/lib/src/rules/directives_ordering.dart
@@ -9,9 +9,8 @@
 
 const _desc = r'Adhere to Effective Dart Guide directives sorting conventions.';
 const _details = r'''
-
-**DO** follow the conventions in the 
-[Effective Dart Guide](https://dart.dev/guides/language/effective-dart/style#ordering)
+**DO** follow the directive ordering conventions in
+[Effective Dart](https://dart.dev/guides/language/effective-dart/style#ordering):
 
 **DO** place `dart:` imports before other imports.
 
@@ -108,26 +107,30 @@
 import 'a/b.dart'; // OK
 ```
 ''';
-const _directiveSectionOrderedAlphabetically =
-    'Sort directive sections alphabetically.';
-
-const _exportDirectiveAfterImportDirectives =
-    'Specify exports in a separate section after all imports.';
 
 const _exportKeyword = 'export';
 
 const _importKeyword = 'import';
 
-String _dartDirectiveGoFirst(String type) =>
-    "Place 'dart:' ${type}s before other ${type}s.";
+/// Compares directives by package then file in package.
+///
+/// Package is everything until the first `/`.
+int compareDirectives(String a, String b) {
+  var indexA = a.indexOf('/');
+  var indexB = b.indexOf('/');
+  if (indexA == -1 || indexB == -1) return a.compareTo(b);
+  var result = a.substring(0, indexA).compareTo(b.substring(0, indexB));
+  if (result != 0) return result;
+  return a.substring(indexA + 1).compareTo(b.substring(indexB + 1));
+}
 
 bool _isAbsoluteDirective(NamespaceDirective node) {
-  var uriContent = node.uriContent;
+  var uriContent = node.uri.stringValue;
   return uriContent != null && uriContent.contains(':');
 }
 
 bool _isDartDirective(NamespaceDirective node) {
-  var uriContent = node.uriContent;
+  var uriContent = node.uri.stringValue;
   return uriContent != null && uriContent.startsWith('dart:');
 }
 
@@ -136,7 +139,7 @@
 bool _isNotDartDirective(NamespaceDirective node) => !_isDartDirective(node);
 
 bool _isPackageDirective(NamespaceDirective node) {
-  var uriContent = node.uriContent;
+  var uriContent = node.uri.stringValue;
   return uriContent != null && uriContent.startsWith('package:');
 }
 
@@ -145,12 +148,19 @@
 bool _isRelativeDirective(NamespaceDirective node) =>
     !_isAbsoluteDirective(node);
 
-String _packageDirectiveBeforeRelative(String type) =>
-    "Place 'package:' ${type}s before relative ${type}s.";
+class DirectivesOrdering extends LintRule {
+  static const LintCode dartDirectiveGoFirst =
+      LintCode('directives_ordering', "Place 'dart:' {0}s before other {0}s.");
 
-class DirectivesOrdering extends LintRule
-    implements ProjectVisitor, NodeLintRule {
-  DartProject? project;
+  static const LintCode directiveSectionOrderedAlphabetically = LintCode(
+      'directives_ordering', 'Sort directive sections alphabetically.');
+
+  static const LintCode exportDirectiveAfterImportDirectives = LintCode(
+      'directives_ordering',
+      'Specify exports in a separate section after all imports.');
+
+  static const LintCode packageDirectiveBeforeRelative = LintCode(
+      'directives_ordering', "Place 'package:' {0}s before relative {0}s.");
 
   DirectivesOrdering()
       : super(
@@ -160,53 +170,48 @@
             group: Group.style);
 
   @override
-  ProjectVisitor getProjectVisitor() => this;
-
-  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
     registry.addCompilationUnit(this, visitor);
   }
 
-  @override
-  void visit(DartProject project) {
-    this.project = project;
-  }
-
   void _reportLintWithDartDirectiveGoFirstMessage(AstNode node, String type) {
-    _reportLintWithDescription(node, _dartDirectiveGoFirst(type));
-  }
-
-  void _reportLintWithDescription(AstNode node, String description) {
-    reporter.reportErrorForNode(LintCode(name, description), node, []);
+    reportLint(node,
+        errorCode: DirectivesOrdering.dartDirectiveGoFirst, arguments: [type]);
   }
 
   void _reportLintWithDirectiveSectionOrderedAlphabeticallyMessage(
       AstNode node) {
-    _reportLintWithDescription(node, _directiveSectionOrderedAlphabetically);
+    reportLint(node,
+        errorCode: DirectivesOrdering.directiveSectionOrderedAlphabetically);
   }
 
   void _reportLintWithExportDirectiveAfterImportDirectiveMessage(AstNode node) {
-    _reportLintWithDescription(node, _exportDirectiveAfterImportDirectives);
+    reportLint(node,
+        errorCode: DirectivesOrdering.exportDirectiveAfterImportDirectives);
   }
 
   void _reportLintWithPackageDirectiveBeforeRelativeMessage(
       AstNode node, String type) {
-    _reportLintWithDescription(node, _packageDirectiveBeforeRelative(type));
+    reportLint(node,
+        errorCode: DirectivesOrdering.packageDirectiveBeforeRelative,
+        arguments: [type]);
   }
 }
 
+// ignore: unused_element
 class _PackageBox {
   final String _packageName;
 
   _PackageBox(this._packageName);
 
+  // ignore: unused_element
   bool _isNotOwnPackageDirective(NamespaceDirective node) =>
       _isPackageDirective(node) && !_isOwnPackageDirective(node);
 
   bool _isOwnPackageDirective(NamespaceDirective node) {
-    var uriContent = node.uriContent;
+    var uriContent = node.uri.stringValue;
     return uriContent != null &&
         uriContent.startsWith('package:$_packageName/');
   }
@@ -217,8 +222,6 @@
 
   _Visitor(this.rule);
 
-  DartProject? get project => rule.project;
-
   @override
   void visitCompilationUnit(CompilationUnit node) {
     var lintedNodes = <AstNode>{};
@@ -270,33 +273,40 @@
     _checkSectionInOrder(lintedNodes, relativeImports);
     _checkSectionInOrder(lintedNodes, relativeExports);
 
-    var projectName = project?.name;
-    if (projectName == null) {
-      // Not a pub package. Package directives should be sorted in one block.
-      var packageImports = importDirectives.where(_isPackageDirective);
-      var packageExports = exportDirectives.where(_isPackageDirective);
+    // See: https://github.com/dart-lang/linter/issues/3395
+    // (`DartProject` removal)
+    // The rub is that *all* projects are being treated as "not pub"
+    // packages.  We'll want to be careful when fixing this since it
+    // will have ecosystem impact.
 
-      _checkSectionInOrder(lintedNodes, packageImports);
-      _checkSectionInOrder(lintedNodes, packageExports);
-    } else {
-      var packageBox = _PackageBox(projectName);
+    // Not a pub package. Package directives should be sorted in one block.
+    var packageImports = importDirectives.where(_isPackageDirective);
+    var packageExports = exportDirectives.where(_isPackageDirective);
 
-      var thirdPartyPackageImports =
-          importDirectives.where(packageBox._isNotOwnPackageDirective);
-      var thirdPartyPackageExports =
-          exportDirectives.where(packageBox._isNotOwnPackageDirective);
+    _checkSectionInOrder(lintedNodes, packageImports);
+    _checkSectionInOrder(lintedNodes, packageExports);
 
-      var ownPackageImports =
-          importDirectives.where(packageBox._isOwnPackageDirective);
-      var ownPackageExports =
-          exportDirectives.where(packageBox._isOwnPackageDirective);
-
-      _checkSectionInOrder(lintedNodes, thirdPartyPackageImports);
-      _checkSectionInOrder(lintedNodes, thirdPartyPackageExports);
-
-      _checkSectionInOrder(lintedNodes, ownPackageImports);
-      _checkSectionInOrder(lintedNodes, ownPackageExports);
-    }
+    // The following is relying on projectName which is meant to come from
+    // a `DartProject` instance (but was not since the project was always null)
+    // else {
+    //   var packageBox = _PackageBox(projectName);
+    //
+    //   var thirdPartyPackageImports =
+    //       importDirectives.where(packageBox._isNotOwnPackageDirective);
+    //   var thirdPartyPackageExports =
+    //       exportDirectives.where(packageBox._isNotOwnPackageDirective);
+    //
+    //   var ownPackageImports =
+    //       importDirectives.where(packageBox._isOwnPackageDirective);
+    //   var ownPackageExports =
+    //       exportDirectives.where(packageBox._isOwnPackageDirective);
+    //
+    //   _checkSectionInOrder(lintedNodes, thirdPartyPackageImports);
+    //   _checkSectionInOrder(lintedNodes, thirdPartyPackageExports);
+    //
+    //   _checkSectionInOrder(lintedNodes, ownPackageImports);
+    //   _checkSectionInOrder(lintedNodes, ownPackageExports);
+    // }
   }
 
   void _checkExportDirectiveAfterImportDirective(
@@ -355,8 +365,8 @@
     NamespaceDirective? previousDirective;
     for (var directive in nodes) {
       if (previousDirective != null) {
-        var previousUri = previousDirective.uriContent;
-        var directiveUri = directive.uriContent;
+        var previousUri = previousDirective.uri.stringValue;
+        var directiveUri = directive.uri.stringValue;
         if (previousUri != null &&
             directiveUri != null &&
             compareDirectives(previousUri, directiveUri) > 0) {
@@ -373,15 +383,3 @@
   Iterable<ImportDirective> _getImportDirectives(CompilationUnit node) =>
       node.directives.whereType<ImportDirective>();
 }
-
-/// Compares directives by package then file in package.
-///
-/// Package is everything until the first `/`.
-int compareDirectives(String a, String b) {
-  var indexA = a.indexOf('/');
-  var indexB = b.indexOf('/');
-  if (indexA == -1 || indexB == -1) return a.compareTo(b);
-  var result = a.substring(0, indexA).compareTo(b.substring(0, indexB));
-  if (result != 0) return result;
-  return a.substring(indexA + 1).compareTo(b.substring(indexB + 1));
-}
diff --git a/lib/src/rules/discarded_futures.dart b/lib/src/rules/discarded_futures.dart
new file mode 100644
index 0000000..6f254dc
--- /dev/null
+++ b/lib/src/rules/discarded_futures.dart
@@ -0,0 +1,166 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+
+import '../analyzer.dart';
+
+const _desc = r"Don't invoke asynchronous functions in non-async blocks.";
+
+const _details = r'''
+Making asynchronous calls in non-`async` functions is usually the sign of a
+programming error.  In general these functions should be marked `async` and such
+futures should likely be awaited (as enforced by `unawaited_futures`).
+
+**DON'T** invoke asynchronous functions in non-`async` blocks.
+
+**BAD:**
+```dart
+void recreateDir(String path) {
+  deleteDir(path);
+  createDir(path);
+}
+
+Future<void> deleteDir(String path) async {}
+
+Future<void> createDir(String path) async {}
+```
+
+**GOOD:**
+```dart
+Future<void> recreateDir(String path) async {
+  await deleteDir(path);
+  await createDir(path);
+}
+
+Future<void> deleteDir(String path) async {}
+
+Future<void> createDir(String path) async {}
+```
+''';
+
+class DiscardedFutures extends LintRule {
+  DiscardedFutures()
+      : super(
+            name: 'discarded_futures',
+            description: _desc,
+            details: _details,
+            group: Group.errors);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addConstructorDeclaration(this, visitor);
+    registry.addFieldDeclaration(this, visitor);
+    registry.addFunctionDeclaration(this, visitor);
+    registry.addMethodDeclaration(this, visitor);
+    registry.addTopLevelVariableDeclaration(this, visitor);
+  }
+}
+
+class _InvocationVisitor extends RecursiveAstVisitor<void> {
+  final LintRule rule;
+  _InvocationVisitor(this.rule);
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    if (node.body.isAsynchronous) return;
+    super.visitFunctionExpression(node);
+  }
+
+  @override
+  void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
+    if (node.staticInvokeType.isFuture) {
+      rule.reportLint(node.function);
+    }
+    super.visitFunctionExpressionInvocation(node);
+  }
+
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    if (node.methodName.staticElement.isDartAsyncUnawaited) return;
+    if (node.staticInvokeType.isFuture) {
+      rule.reportLint(node.methodName);
+    }
+    super.visitMethodInvocation(node);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  void check(FunctionBody body) {
+    if (body.isAsynchronous) return;
+    var visitor = _InvocationVisitor(rule);
+    body.accept(visitor);
+  }
+
+  void checkVariables(VariableDeclarationList variables) {
+    if (variables.type?.type?.isFuture ?? false) return;
+    for (var variable in variables.variables) {
+      var initializer = variable.initializer;
+      if (initializer is FunctionExpression) {
+        check(initializer.body);
+      }
+    }
+  }
+
+  @override
+  void visitConstructorDeclaration(ConstructorDeclaration node) {
+    check(node.body);
+  }
+
+  @override
+  void visitFieldDeclaration(FieldDeclaration node) {
+    checkVariables(node.fields);
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    if (node.returnType?.type.isFuture ?? false) return;
+    check(node.functionExpression.body);
+  }
+
+  @override
+  void visitMethodDeclaration(MethodDeclaration node) {
+    if (node.returnType?.type.isFuture ?? false) return;
+    check(node.body);
+  }
+
+  @override
+  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+    checkVariables(node.variables);
+  }
+}
+
+extension on DartType? {
+  bool get isFuture {
+    var self = this;
+    DartType? returnType;
+    if (self is FunctionType) {
+      returnType = self.returnType;
+    }
+    if (self is InterfaceType) {
+      returnType = self;
+    }
+
+    return returnType != null &&
+        (returnType.isDartAsyncFuture || returnType.isDartAsyncFutureOr);
+  }
+}
+
+extension ElementExtension on Element? {
+  bool get isDartAsyncUnawaited {
+    var self = this;
+    return self is FunctionElement &&
+        self.name == 'unawaited' &&
+        self.library.isDartAsync;
+  }
+}
diff --git a/lib/src/rules/do_not_use_environment.dart b/lib/src/rules/do_not_use_environment.dart
index f3e9f78..e6560b2 100644
--- a/lib/src/rules/do_not_use_environment.dart
+++ b/lib/src/rules/do_not_use_environment.dart
@@ -13,7 +13,7 @@
 Using values derived from the environment at compile-time, creates
 hidden global state and makes applications hard to understand and maintain.
 
-**DO NOT** use `fromEnvironment` or `hasEnvironment` factory constructors.
+**DON'T** use `fromEnvironment` or `hasEnvironment` factory constructors.
 
 **BAD:**
 ```dart
@@ -22,7 +22,7 @@
 ```
 ''';
 
-class DoNotUseEnvironment extends LintRule implements NodeLintRule {
+class DoNotUseEnvironment extends LintRule {
   DoNotUseEnvironment()
       : super(
             name: 'do_not_use_environment',
diff --git a/lib/src/rules/empty_catches.dart b/lib/src/rules/empty_catches.dart
index 9d36036..2c22b57 100644
--- a/lib/src/rules/empty_catches.dart
+++ b/lib/src/rules/empty_catches.dart
@@ -6,12 +6,11 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../utils.dart';
+import '../util/ascii_utils.dart';
 
 const _desc = r'Avoid empty catch blocks.';
 
 const _details = r'''
-
 **AVOID** empty catch blocks.
 
 In general, empty catch blocks should be avoided.  In cases where they are
@@ -49,7 +48,12 @@
 
 ''';
 
-class EmptyCatches extends LintRule implements NodeLintRule {
+class EmptyCatches extends LintRule {
+  static const LintCode code = LintCode('empty_catches', 'Empty catch block.',
+      correctionMessage:
+          'Try adding statements to the block, adding a comment to the block, '
+          "or removing the 'catch' clause.");
+
   EmptyCatches()
       : super(
             name: 'empty_catches',
@@ -58,6 +62,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -75,7 +82,7 @@
     // Skip exceptions named with underscores.
     var exceptionParameter = node.exceptionParameter;
     if (exceptionParameter != null &&
-        isJustUnderscores(exceptionParameter.name)) {
+        exceptionParameter.name.lexeme.isJustUnderscores) {
       return;
     }
 
diff --git a/lib/src/rules/empty_constructor_bodies.dart b/lib/src/rules/empty_constructor_bodies.dart
index 121cd41..8b71922 100644
--- a/lib/src/rules/empty_constructor_bodies.dart
+++ b/lib/src/rules/empty_constructor_bodies.dart
@@ -10,7 +10,6 @@
 const _desc = r'Use `;` instead of `{}` for empty constructor bodies.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **DO** use `;` instead of `{}` for empty constructor bodies.
@@ -19,14 +18,6 @@
 semicolon.  This is required for const constructors.  For consistency and
 brevity, other constructors should also do this.
 
-**GOOD:**
-```dart
-class Point {
-  int x, y;
-  Point(this.x, this.y);
-}
-```
-
 **BAD:**
 ```dart
 class Point {
@@ -35,9 +26,21 @@
 }
 ```
 
+**GOOD:**
+```dart
+class Point {
+  int x, y;
+  Point(this.x, this.y);
+}
+```
+
 ''';
 
-class EmptyConstructorBodies extends LintRule implements NodeLintRule {
+class EmptyConstructorBodies extends LintRule {
+  static const LintCode code = LintCode('empty_constructor_bodies',
+      "Empty constructor bodies should be written using a ';' rather than '{}'.",
+      correctionMessage: "Try replacing the constructor body with ';'.");
+
   EmptyConstructorBodies()
       : super(
             name: 'empty_constructor_bodies',
@@ -46,6 +49,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/empty_statements.dart b/lib/src/rules/empty_statements.dart
index 597efac..984d145 100644
--- a/lib/src/rules/empty_statements.dart
+++ b/lib/src/rules/empty_statements.dart
@@ -10,7 +10,6 @@
 const _desc = r'Avoid empty statements.';
 
 const _details = r'''
-
 **AVOID** empty statements.
 
 Empty statements almost always indicate a bug.
@@ -41,7 +40,12 @@
 
 ''';
 
-class EmptyStatements extends LintRule implements NodeLintRule {
+class EmptyStatements extends LintRule {
+  static const LintCode code = LintCode(
+      'empty_statements', 'Unnecessary empty statement.',
+      correctionMessage:
+          'Try removing the empty statement or restructuring the code.');
+
   EmptyStatements()
       : super(
             name: 'empty_statements',
@@ -50,6 +54,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/enable_null_safety.dart b/lib/src/rules/enable_null_safety.dart
new file mode 100644
index 0000000..13ecc78
--- /dev/null
+++ b/lib/src/rules/enable_null_safety.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Do use sound null safety.';
+
+const _details = r'''
+**DO** use sound null safety, by not specifying a dart version lower than `2.12`.
+
+**BAD:**
+```dart
+// @dart=2.8
+a() {
+}
+```
+
+**GOOD:**
+```dart
+b() {
+}
+```
+''';
+
+class EnableNullSafety extends LintRule implements NodeLintRule {
+  EnableNullSafety()
+      : super(
+            name: 'enable_null_safety',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this, context);
+    registry.addCompilationUnit(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  // to be kept in sync with LanguageVersionOverrideVerifier (added groups for the version)
+  static final regExp = RegExp(r'^\s*//\s*@dart\s*=\s*(\d+)\.(\d+)');
+  final LintRule rule;
+  final LinterContext context;
+
+  _Visitor(this.rule, this.context);
+
+  @override
+  void visitCompilationUnit(CompilationUnit node) {
+    var beginToken = node.beginToken;
+    if (beginToken.type == TokenType.SCRIPT_TAG) {
+      beginToken = beginToken.next!;
+    }
+    CommentToken? comment = beginToken.precedingComments;
+    while (comment != null) {
+      var match = regExp.firstMatch(comment.lexeme);
+      if (match != null && match.groupCount == 2) {
+        var major = int.parse(match.group(1)!);
+        var minor = int.parse(match.group(2)!);
+        if (major == 1 || (major == 2 && minor < 12)) {
+          rule.reportLintForToken(comment);
+        }
+      }
+
+      var next = comment.next;
+      comment = next is CommentToken ? next : null;
+    }
+  }
+}
diff --git a/lib/src/rules/eol_at_end_of_file.dart b/lib/src/rules/eol_at_end_of_file.dart
index ca24250..c0f84bb 100644
--- a/lib/src/rules/eol_at_end_of_file.dart
+++ b/lib/src/rules/eol_at_end_of_file.dart
@@ -10,7 +10,6 @@
 const _desc = r'Put a single newline at end of file.';
 
 const _details = r'''
-
 **DO** put a single newline at the end of non-empty files.
 
 **BAD:**
@@ -24,9 +23,10 @@
 b {
 }
     <-- newline
+```    
 ''';
 
-class EolAtEndOfFile extends LintRule implements NodeLintRule {
+class EolAtEndOfFile extends LintRule {
   EolAtEndOfFile()
       : super(
             name: 'eol_at_end_of_file',
diff --git a/lib/src/rules/exhaustive_cases.dart b/lib/src/rules/exhaustive_cases.dart
index c9271a3..9555da2 100644
--- a/lib/src/rules/exhaustive_cases.dart
+++ b/lib/src/rules/exhaustive_cases.dart
@@ -8,7 +8,7 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Define case clauses for all constants in enum-like classes.';
 
@@ -74,7 +74,11 @@
 ```
 ''';
 
-class ExhaustiveCases extends LintRule implements NodeLintRule {
+class ExhaustiveCases extends LintRule {
+  static const LintCode code = LintCode(
+      'exhaustive_cases', "Missing case clauses for some constants in '{0}'.",
+      correctionMessage: 'Try adding case clauses for the missing constants.');
+
   ExhaustiveCases()
       : super(
             name: 'exhaustive_cases',
@@ -83,6 +87,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -91,12 +98,6 @@
 }
 
 class _Visitor extends SimpleAstVisitor {
-  static const LintCode lintCode = LintCode(
-    'exhaustive_cases',
-    "Missing case clause for '{0}'.",
-    correction: 'Try adding a case clause for the missing constant.',
-  );
-
   final LintRule rule;
 
   _Visitor(this.rule);
@@ -105,27 +106,34 @@
   void visitSwitchStatement(SwitchStatement statement) {
     var expressionType = statement.expression.staticType;
     if (expressionType is InterfaceType) {
-      var classElement = expressionType.element;
+      var interfaceElement = expressionType.element;
       // Handled in analyzer.
-      if (classElement.isEnum) {
+      if (interfaceElement is! ClassElement) {
         return;
       }
-      var enumDescription = DartTypeUtilities.asEnumLikeClass(classElement);
+      var enumDescription = interfaceElement.asEnumLikeClass;
       if (enumDescription == null) {
         return;
       }
 
-      var enumConstantNames = enumDescription.enumConstantNames;
+      var enumConstants = enumDescription.enumConstants;
       for (var member in statement.members) {
         if (member is SwitchCase) {
-          var expression = member.expression;
+          var expression = member.expression.unParenthesized;
           if (expression is Identifier) {
             var element = expression.staticElement;
             if (element is PropertyAccessorElement) {
-              enumConstantNames.remove(element.name);
+              enumConstants.remove(element.variable.computeConstantValue());
+            } else if (element is VariableElement) {
+              enumConstants.remove(element.computeConstantValue());
             }
           } else if (expression is PropertyAccess) {
-            enumConstantNames.remove(expression.propertyName.name);
+            var element = expression.propertyName.staticElement;
+            if (element is PropertyAccessorElement) {
+              enumConstants.remove(element.variable.computeConstantValue());
+            } else if (element is VariableElement) {
+              enumConstants.remove(element.computeConstantValue());
+            }
           }
         }
         if (member is SwitchDefault) {
@@ -133,15 +141,18 @@
         }
       }
 
-      for (var constantName in enumConstantNames) {
-        // Use the same offset as MISSING_ENUM_CONSTANT_IN_SWITCH
+      for (var constant in enumConstants.keys) {
+        // Use the same offset as MISSING_ENUM_CONSTANT_IN_SWITCH.
         var offset = statement.offset;
         var end = statement.rightParenthesis.end;
+        var elements = enumConstants[constant]!;
+        var preferredElement = elements.firstWhere(
+            (element) => !element.hasDeprecated,
+            orElse: () => elements.first);
         rule.reportLintForOffset(
           offset,
           end - offset,
-          arguments: [constantName],
-          errorCode: lintCode,
+          arguments: [preferredElement.name],
         );
       }
     }
diff --git a/lib/src/rules/file_names.dart b/lib/src/rules/file_names.dart
index a1ac107..551e3a5 100644
--- a/lib/src/rules/file_names.dart
+++ b/lib/src/rules/file_names.dart
@@ -11,7 +11,6 @@
 const _desc = r'Name source files using `lowercase_with_underscores`.';
 
 const _details = r'''
-
 **DO** name source files using `lowercase_with_underscores`.
 
 Some file systems are not case-sensitive, so many projects require filenames to
@@ -20,17 +19,17 @@
 a valid Dart identifier, which may be helpful if the language later supports
 symbolic imports.
 
-**GOOD:**
-
-* `slider_menu.dart`
-* `file_system.dart`
-
 **BAD:**
 
 * `SliderMenu.dart`
 * `filesystem.dart`
 * `file-system.dart`
 
+**GOOD:**
+
+* `slider_menu.dart`
+* `file_system.dart`
+
 Files without a strict `.dart` extension are ignored.  For example:
 
 **OK:**
@@ -43,7 +42,12 @@
 
 ''';
 
-class FileNames extends LintRule implements NodeLintRule {
+class FileNames extends LintRule {
+  static const LintCode code = LintCode(
+      'file_names', "The file name '{0}' isn't a snake_case identifier.",
+      correctionMessage:
+          'Try changing the name to follow the snake_case style.');
+
   FileNames()
       : super(
             name: 'file_names',
@@ -52,6 +56,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -70,7 +77,7 @@
     if (declaredElement != null) {
       var fileName = declaredElement.source.shortName;
       if (!isValidDartFileName(fileName)) {
-        rule.reportLint(node);
+        rule.reportLintForOffset(0, 0, arguments: [fileName]);
       }
     }
   }
diff --git a/lib/src/rules/flutter_style_todos.dart b/lib/src/rules/flutter_style_todos.dart
index 910601f..2f7584c 100644
--- a/lib/src/rules/flutter_style_todos.dart
+++ b/lib/src/rules/flutter_style_todos.dart
@@ -12,8 +12,7 @@
     '// TODO(username): message, https://URL-to-issue.';
 
 const _details = r'''
-
-**DO** Use Flutter TODO format.
+**DO** use Flutter TODO format.
 
 From the [Flutter docs](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#comments):
 
@@ -27,7 +26,7 @@
 
 ''';
 
-class FlutterStyleTodos extends LintRule implements NodeLintRule {
+class FlutterStyleTodos extends LintRule {
   FlutterStyleTodos()
       : super(
             name: 'flutter_style_todos',
@@ -44,34 +43,34 @@
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  static final _todoRegExp = RegExp(r'//+(.* )?TODO\b');
+  static final _todoRegExp = RegExp(r'//+(.* )?TODO\b', caseSensitive: false);
 
   static final _todoExpectedRegExp =
-      RegExp(r'// TODO\([a-zA-Z][-a-zA-Z0-9]*\): ');
+      RegExp(r'// TODO\([a-zA-Z0-9][-a-zA-Z0-9]*\): ');
 
   final LintRule rule;
 
   _Visitor(this.rule);
 
+  void checkComments(Token token) {
+    Token? comment = token.precedingComments;
+    while (comment != null) {
+      _checkComment(comment);
+      comment = comment.next;
+    }
+  }
+
   @override
   void visitCompilationUnit(CompilationUnit node) {
     Token? token = node.beginToken;
     while (token != null) {
-      _getPrecedingComments(token).forEach(_visitComment);
+      checkComments(token);
       if (token == token.next) break;
       token = token.next;
     }
   }
 
-  Iterable<Token> _getPrecedingComments(Token token) sync* {
-    Token? comment = token.precedingComments;
-    while (comment != null) {
-      yield comment;
-      comment = comment.next;
-    }
-  }
-
-  void _visitComment(Token node) {
+  void _checkComment(Token node) {
     var content = node.lexeme;
     if (content.startsWith(_todoRegExp) &&
         !content.startsWith(_todoExpectedRegExp)) {
diff --git a/lib/src/rules/hash_and_equals.dart b/lib/src/rules/hash_and_equals.dart
index 7094153..a45deaa 100644
--- a/lib/src/rules/hash_and_equals.dart
+++ b/lib/src/rules/hash_and_equals.dart
@@ -11,7 +11,6 @@
 const _desc = r'Always override `hashCode` if overriding `==`.';
 
 const _details = r'''
-
 **DO** override `hashCode` if overriding `==` and prefer overriding `==` if
 overriding `hashCode`.
 
@@ -39,7 +38,10 @@
   Better(this.value);
 
   @override
-  bool operator ==(Object other) => other is Better && other.value == value;
+  bool operator ==(Object other) =>
+      other is Better &&
+      other.runtimeType == runtimeType &&
+      other.value == value;
 
   @override
   int get hashCode => value.hashCode;
@@ -47,7 +49,11 @@
 ```
 ''';
 
-class HashAndEquals extends LintRule implements NodeLintRule {
+class HashAndEquals extends LintRule {
+  static const LintCode code = LintCode(
+      'hash_and_equals', "Missing a corresponding override of '{0}'.",
+      correctionMessage: "Try overriding '{0}' or removing '{1}'.");
+
   HashAndEquals()
       : super(
             name: 'hash_and_equals',
@@ -56,6 +62,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -64,22 +73,19 @@
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  static const LintCode hashMemberCode = LintCode(
-      'hash_and_equals', 'Override `==` if overriding `hashCode`.',
-      correction: 'Implement `==`.');
-  static const LintCode equalsMemberCode = LintCode(
-      'hash_and_equals', 'Override `hashCode` if overriding `==`.',
-      correction: 'Implement `hashCode`.');
-
   final LintRule rule;
 
   _Visitor(this.rule);
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
+    _check(node.members);
+  }
+
+  void _check(NodeList<ClassMember> members) {
     MethodDeclaration? eq;
     ClassMember? hash;
-    for (var member in node.members) {
+    for (var member in members) {
       if (isEquals(member)) {
         eq = member as MethodDeclaration;
       } else if (isHashCode(member)) {
@@ -88,14 +94,14 @@
     }
 
     if (eq != null && hash == null) {
-      rule.reportLint(eq.name, errorCode: equalsMemberCode);
+      rule.reportLintForToken(eq.name, arguments: ['hashCode', '==']);
     }
     if (hash != null && eq == null) {
       if (hash is MethodDeclaration) {
-        rule.reportLint(hash.name, errorCode: hashMemberCode);
+        rule.reportLintForToken(hash.name, arguments: ['==', 'hashCode']);
       } else if (hash is FieldDeclaration) {
-        rule.reportLint(getFieldIdentifier(hash, 'hashCode'),
-            errorCode: hashMemberCode);
+        rule.reportLintForToken(getFieldName(hash, 'hashCode'),
+            arguments: ['==', 'hashCode']);
       }
     }
   }
diff --git a/lib/src/rules/implementation_imports.dart b/lib/src/rules/implementation_imports.dart
index 7cb80a8..8dbc11e 100644
--- a/lib/src/rules/implementation_imports.dart
+++ b/lib/src/rules/implementation_imports.dart
@@ -10,7 +10,6 @@
 const _desc = r"Don't import implementation files from another package.";
 
 const _details = r'''
-
 From the the [pub package layout doc](https://dart.dev/tools/pub/package-layout#implementation-files):
 
 **DON'T** import implementation files from another package.
@@ -30,7 +29,7 @@
 **BAD:**
 ```dart
 // In 'road_runner'
-import 'package:acme/lib/src/internals.dart;
+import 'package:acme/src/internals.dart';
 ```
 
 ''';
@@ -56,10 +55,16 @@
   if (segments1.isEmpty || segments2.isEmpty) {
     return false;
   }
-  return segments1[0] == segments2[0];
+  return segments1.first == segments2.first;
 }
 
-class ImplementationImports extends LintRule implements NodeLintRule {
+class ImplementationImports extends LintRule {
+  static const LintCode code = LintCode('implementation_imports',
+      "Import of a library in the 'lib/src' directory of another package.",
+      correctionMessage:
+          'Try importing a public library that exports this library, or '
+          'removing the import.');
+
   ImplementationImports()
       : super(
             name: 'implementation_imports',
@@ -68,6 +73,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -82,7 +90,7 @@
 
   @override
   void visitImportDirective(ImportDirective node) {
-    var importUri = node.uriSource?.uri;
+    var importUri = node.element?.importedLibrary?.source.uri;
     var sourceUri = node.element?.source.uri;
 
     // Test for 'package:*/src/'.
diff --git a/lib/src/rules/implicit_call_tearoffs.dart b/lib/src/rules/implicit_call_tearoffs.dart
new file mode 100644
index 0000000..747f81a
--- /dev/null
+++ b/lib/src/rules/implicit_call_tearoffs.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc =
+    r'Explicitly tear-off `call` methods when using an object as a Function.';
+
+const _details = r'''
+**DO**
+Explicitly tear off `.call` methods from objects when assigning to a Function
+type. There is less magic with an explicit tear off. Future language versions
+may remove the implicit call tear off.
+
+**BAD:**
+```dart
+class Callable {
+  void call() {}
+}
+void callIt(void Function() f) {
+  f();
+}
+
+callIt(Callable());
+```
+
+**GOOD:**
+```dart
+class Callable {
+  void call() {}
+}
+void callIt(void Function() f) {
+  f();
+}
+
+callIt(Callable().call);
+```
+
+''';
+
+class ImplicitCallTearoffs extends LintRule {
+  ImplicitCallTearoffs()
+      : super(
+          name: 'implicit_call_tearoffs',
+          description: _desc,
+          details: _details,
+          group: Group.style,
+          maturity: Maturity.experimental,
+        );
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addImplicitCallReference(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitImplicitCallReference(ImplicitCallReference node) {
+    rule.reportLint(node);
+  }
+}
diff --git a/lib/src/rules/invariant_booleans.dart b/lib/src/rules/invariant_booleans.dart
index 1e2ede2..0b559f9 100644
--- a/lib/src/rules/invariant_booleans.dart
+++ b/lib/src/rules/invariant_booleans.dart
@@ -7,15 +7,17 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
+import '../extensions.dart';
 import '../util/boolean_expression_utilities.dart';
 import '../util/condition_scope_visitor.dart';
-import '../util/dart_type_utilities.dart';
 import '../util/tested_expressions.dart';
 
 const _desc =
     r'Conditions should not unconditionally evaluate to `true` or to `false`.';
 
 const _details = r'''
+**DEPRECATED:** This rule is unmaintained and will be removed in a future Linter
+release.
 
 **DON'T** test for conditions that can be inferred at compile time or test the
 same condition twice.
@@ -98,20 +100,20 @@
 
 ''';
 
-Iterable<Element?> _getElementsInExpression(Expression node) =>
-    DartTypeUtilities.traverseNodesInDFS(node)
-        .map(DartTypeUtilities.getCanonicalElementFromIdentifier)
-        .where((e) => e != null);
+Iterable<Element?> _getElementsInExpression(Expression node) => node
+    // todo(pq): migrate away from `traverseNodesInDFS` (https://github.com/dart-lang/linter/issues/3745)
+    // ignore: deprecated_member_use_from_same_package
+    .traverseNodesInDFS()
+    .map((e) => e.canonicalElement)
+    .where((e) => e != null);
 
-class InvariantBooleans extends LintRule implements NodeLintRule {
+class InvariantBooleans extends LintRule {
   InvariantBooleans()
       : super(
             name: 'invariant_booleans',
             description: _desc,
             details: _details,
-            // This rule is experimental until there are fewer "false positive"
-            // bugs, and performance has been improved.
-            maturity: Maturity.experimental,
+            maturity: Maturity.deprecated,
             group: Group.errors);
 
   @override
diff --git a/lib/src/rules/iterable_contains_unrelated_type.dart b/lib/src/rules/iterable_contains_unrelated_type.dart
index 7a46d03..7471929 100644
--- a/lib/src/rules/iterable_contains_unrelated_type.dart
+++ b/lib/src/rules/iterable_contains_unrelated_type.dart
@@ -3,14 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
 import '../util/unrelated_types_visitor.dart';
 
 const _desc = r'Invocation of Iterable<E>.contains with references of unrelated'
     r' types.';
 
 const _details = r'''
-
 **DON'T** invoke `contains` on `Iterable` with an instance of different type
 than the parameter type.
 
@@ -119,7 +117,10 @@
 
 ''';
 
-class IterableContainsUnrelatedType extends LintRule implements NodeLintRule {
+class IterableContainsUnrelatedType extends LintRule {
+  static const LintCode code = LintCode('iterable_contains_unrelated_type',
+      "The argument type '{0}' isn't related to '{1}'.");
+
   IterableContainsUnrelatedType()
       : super(
             name: 'iterable_contains_unrelated_type',
@@ -128,21 +129,25 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this, context.typeSystem);
+    var visitor = _Visitor(this, context.typeSystem, context.typeProvider);
     registry.addMethodInvocation(this, visitor);
   }
 }
 
 class _Visitor extends UnrelatedTypesProcessors {
-  static final _definition = InterfaceTypeDefinition('Iterable', 'dart.core');
-
-  _Visitor(LintRule rule, TypeSystem typeSystem) : super(rule, typeSystem);
+  _Visitor(super.rule, super.typeSystem, super.typeProvider);
 
   @override
-  InterfaceTypeDefinition get definition => _definition;
-
-  @override
-  String get methodName => 'contains';
+  List<MethodDefinition> get methods => [
+        MethodDefinitionForElement(
+          typeProvider.iterableElement,
+          'contains',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        )
+      ];
 }
diff --git a/lib/src/rules/join_return_with_assignment.dart b/lib/src/rules/join_return_with_assignment.dart
index b8755d7..f5c8c02 100644
--- a/lib/src/rules/join_return_with_assignment.dart
+++ b/lib/src/rules/join_return_with_assignment.dart
@@ -6,12 +6,11 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../util/dart_type_utilities.dart' as type_utils;
 
 const _desc = r'Join return statement with assignment when possible.';
 
 const _details = r'''
-
 **DO** join return statement with assignment when possible.
 
 **BAD:**
@@ -36,15 +35,23 @@
 ''';
 
 Expression? _getExpressionFromAssignmentStatement(Statement node) {
-  var visitor = _AssignmentStatementVisitor();
-  node.accept(visitor);
-  return visitor.expression;
+  if (node is ExpressionStatement) {
+    var expression = node.expression.unParenthesized;
+    if (expression is AssignmentExpression) {
+      return expression.leftHandSide;
+    } else if (expression is PostfixExpression) {
+      return expression.operand;
+    } else if (expression is PrefixExpression) {
+      return expression.operand;
+    }
+  }
+  return null;
 }
 
 Expression? _getExpressionFromReturnStatement(Statement node) =>
     node is ReturnStatement ? node.expression : null;
 
-class JoinReturnWithAssignment extends LintRule implements NodeLintRule {
+class JoinReturnWithAssignment extends LintRule {
   JoinReturnWithAssignment()
       : super(
             name: 'join_return_with_assignment',
@@ -60,34 +67,6 @@
   }
 }
 
-class _AssignmentStatementVisitor extends SimpleAstVisitor {
-  Expression? expression;
-  @override
-  void visitAssignmentExpression(AssignmentExpression node) {
-    expression = node.leftHandSide;
-  }
-
-  @override
-  void visitExpressionStatement(ExpressionStatement statement) {
-    statement.expression.accept(this);
-  }
-
-  @override
-  void visitParenthesizedExpression(ParenthesizedExpression node) {
-    node.unParenthesized.accept(this);
-  }
-
-  @override
-  void visitPostfixExpression(PostfixExpression node) {
-    expression = node.operand;
-  }
-
-  @override
-  void visitPrefixExpression(PrefixExpression node) {
-    expression = node.operand;
-  }
-}
-
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
@@ -100,11 +79,7 @@
     if (length < 2) {
       return;
     }
-    var secondLastStatement = statements[length - 2];
-    var lastStatement = statements.last;
-    var secondLastExpression =
-        _getExpressionFromAssignmentStatement(secondLastStatement);
-    var lastExpression = _getExpressionFromReturnStatement(lastStatement);
+    var lastExpression = _getExpressionFromReturnStatement(statements.last);
 
     // In this case, the last statement was not a return statement with a
     // simple target.
@@ -112,15 +87,23 @@
       return;
     }
 
+    var secondLastStatement = statements[length - 2];
+    var secondLastExpression =
+        _getExpressionFromAssignmentStatement(secondLastStatement);
+    // Return if the second-to-last statement was not an assignment.
+    if (secondLastExpression == null) {
+      return;
+    }
+
     Expression? thirdLastExpression;
     if (length >= 3) {
       var thirdLastStatement = statements[length - 3];
       thirdLastExpression =
           _getExpressionFromAssignmentStatement(thirdLastStatement);
     }
-    if (!DartTypeUtilities.canonicalElementsFromIdentifiersAreEqual(
+    if (!type_utils.canonicalElementsFromIdentifiersAreEqual(
             secondLastExpression, thirdLastExpression) &&
-        DartTypeUtilities.canonicalElementsFromIdentifiersAreEqual(
+        type_utils.canonicalElementsFromIdentifiersAreEqual(
             lastExpression, secondLastExpression)) {
       rule.reportLint(secondLastStatement);
     }
diff --git a/lib/src/rules/leading_newlines_in_multiline_strings.dart b/lib/src/rules/leading_newlines_in_multiline_strings.dart
index a08e057..eea90f1 100644
--- a/lib/src/rules/leading_newlines_in_multiline_strings.dart
+++ b/lib/src/rules/leading_newlines_in_multiline_strings.dart
@@ -29,13 +29,12 @@
   "b": 2
 }''';
 
-var s2 = '''This onliner multiline string is ok. It usually allows to escape both ' and " in the string.''';
+var s2 = '''This one-liner multiline string is ok. It usually allows to escape both ' and " in the string.''';
 ```
 
 """;
 
-class LeadingNewlinesInMultilineStrings extends LintRule
-    implements NodeLintRule {
+class LeadingNewlinesInMultilineStrings extends LintRule {
   LeadingNewlinesInMultilineStrings()
       : super(
             name: 'leading_newlines_in_multiline_strings',
diff --git a/lib/src/rules/library_annotations.dart b/lib/src/rules/library_annotations.dart
new file mode 100644
index 0000000..54521b6
--- /dev/null
+++ b/lib/src/rules/library_annotations.dart
@@ -0,0 +1,131 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+// ignore: implementation_imports
+import 'package:analyzer/src/dart/element/extensions.dart';
+import 'package:meta/meta_meta.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Attach library annotations to library directives.';
+
+const _details = r'''
+Attach library annotations to library directives, rather than
+some other library-level element.
+
+**BAD:**
+```dart
+@TestOn('browser')
+
+import 'package:test/test.dart';
+
+void main() {}
+```
+
+**GOOD:**
+```dart
+@TestOn('browser')
+library;
+
+import 'package:test/test.dart';
+
+void main() {}
+```
+
+**NOTE:** An unnamed library, like `library;` above, is only supported in Dart
+2.19 and later. Code which might run in earlier versions of Dart will need to
+provide a name in the `library` directive.
+''';
+
+class LibraryAnnotations extends LintRule {
+  static const LintCode code = LintCode('library_annotations',
+      'This annotation must be attached to a library directive.',
+      correctionMessage:
+          'Try attaching library annotations to library directives.');
+
+  LibraryAnnotations()
+      : super(
+            name: 'library_annotations',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addCompilationUnit(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LibraryAnnotations rule;
+
+  _Visitor(this.rule);
+
+  Directive? firstDirective;
+
+  @override
+  void visitCompilationUnit(CompilationUnit node) {
+    if (node.directives.isNotEmpty) {
+      firstDirective = node.directives.first;
+    }
+    for (var directive in node.directives) {
+      if (directive is PartDirective) {
+        return;
+      }
+      if (directive is! LibraryDirective) {
+        _check(directive);
+      }
+    }
+
+    node.declarations.forEach(_check);
+  }
+
+  void _check(AnnotatedNode node) {
+    for (var annotation in node.metadata) {
+      var elementAnnotation = annotation.elementAnnotation;
+      if (elementAnnotation == null) {
+        return;
+      }
+
+      if (elementAnnotation.targetKinds.length == 1 &&
+          elementAnnotation.targetKinds.contains(TargetKind.library) &&
+          firstDirective == node) {
+        rule.reportLint(annotation);
+      } else if (elementAnnotation.isPragmaLateTrust) {
+        rule.reportLint(annotation);
+      }
+    }
+  }
+}
+
+extension on ElementAnnotation {
+  /// Whether this is an annotation of the form `@pragma('dart2js:late:trust')`.
+  bool get isPragmaLateTrust {
+    if (_isConstructor(libraryName: 'dart.core', className: 'pragma')) {
+      var value = computeConstantValue();
+      var nameValue = value?.getField('name');
+      return nameValue?.toStringValue() == 'dart2js:late:trust';
+    }
+    return false;
+  }
+
+  // Copied from package:analyzer/src/dart/element/element.dart
+  bool _isConstructor({
+    required String libraryName,
+    required String className,
+  }) {
+    var element = this.element;
+    return element is ConstructorElement &&
+        element.enclosingElement.name == className &&
+        element.library.name == libraryName;
+  }
+}
diff --git a/lib/src/rules/library_names.dart b/lib/src/rules/library_names.dart
index 22635dc..db987f8 100644
--- a/lib/src/rules/library_names.dart
+++ b/lib/src/rules/library_names.dart
@@ -11,7 +11,6 @@
 const _desc = r'Name libraries using `lowercase_with_underscores`.';
 
 const _details = r'''
-
 **DO** name libraries using `lowercase_with_underscores`.
 
 Some file systems are not case-sensitive, so many projects require filenames to
@@ -20,22 +19,27 @@
 a valid Dart identifier, which may be helpful if the language later supports
 symbolic imports.
 
-**GOOD:**
-```dart
-library peg_parser;
-```
-
 **BAD:**
 ```dart
 library peg-parser;
 ```
 
+**GOOD:**
+```dart
+library peg_parser;
+```
+
 The lint `file_names` can be used to enforce the same kind of naming on the
 file.
 
 ''';
 
-class LibraryNames extends LintRule implements NodeLintRule {
+class LibraryNames extends LintRule {
+  static const LintCode code = LintCode(
+      'library_names', "The library name '{0}' isn't a snake_case identifier.",
+      correctionMessage:
+          'Try changing the name to follow the snake_case style.');
+
   LibraryNames()
       : super(
             name: 'library_names',
@@ -44,6 +48,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -58,8 +65,9 @@
 
   @override
   void visitLibraryDirective(LibraryDirective node) {
-    if (!isLowerCaseUnderScoreWithDots(node.name.toString())) {
-      rule.reportLint(node.name);
+    var name = node.name2;
+    if (name != null && !isLowerCaseUnderScoreWithDots(name.toString())) {
+      rule.reportLint(name, arguments: [name.toString()]);
     }
   }
 }
diff --git a/lib/src/rules/library_prefixes.dart b/lib/src/rules/library_prefixes.dart
index e986a36..e9dded7 100644
--- a/lib/src/rules/library_prefixes.dart
+++ b/lib/src/rules/library_prefixes.dart
@@ -12,17 +12,8 @@
     r'Use `lowercase_with_underscores` when specifying a library prefix.';
 
 const _details = r'''
-
 **DO** use `lowercase_with_underscores` when specifying a library prefix.
 
-**GOOD:**
-```dart
-import 'dart:math' as math;
-import 'dart:json' as json;
-import 'package:js/js.dart' as js;
-import 'package:javascript_utils/javascript_utils.dart' as js_utils;
-```
-
 **BAD:**
 ```dart
 import 'dart:math' as Math;
@@ -31,9 +22,22 @@
 import 'package:javascript_utils/javascript_utils.dart' as jsUtils;
 ```
 
+**GOOD:**
+```dart
+import 'dart:math' as math;
+import 'dart:json' as json;
+import 'package:js/js.dart' as js;
+import 'package:javascript_utils/javascript_utils.dart' as js_utils;
+```
+
 ''';
 
-class LibraryPrefixes extends LintRule implements NodeLintRule {
+class LibraryPrefixes extends LintRule {
+  static const LintCode code = LintCode(
+      'library_prefixes', "The prefix '{0}' isn't a snake_case identifier.",
+      correctionMessage:
+          'Try changing the prefix to follow the snake_case style.');
+
   LibraryPrefixes()
       : super(
             name: 'library_prefixes',
@@ -42,6 +46,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -56,8 +63,9 @@
 
   @override
   void visitImportDirective(ImportDirective node) {
-    if (node.prefix != null && !isValidLibraryPrefix(node.prefix.toString())) {
-      rule.reportLint(node.prefix);
+    var prefix = node.prefix;
+    if (prefix != null && !isValidLibraryPrefix(prefix.toString())) {
+      rule.reportLint(prefix, arguments: [prefix.toString()]);
     }
   }
 }
diff --git a/lib/src/rules/library_private_types_in_public_api.dart b/lib/src/rules/library_private_types_in_public_api.dart
index 21bec48..7bada33 100644
--- a/lib/src/rules/library_private_types_in_public_api.dart
+++ b/lib/src/rules/library_private_types_in_public_api.dart
@@ -5,13 +5,13 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
 
 const _desc = r'Avoid using private types in public APIs.';
 
 const _details = r'''
-
 **AVOID** using library private types in public APIs.
 
 For the purposes of this lint, a public API is considered to be any top-level or
@@ -27,20 +27,26 @@
   `typedef F = _Private Function();`), or
 - any type used in the `on` clause of an extension or a mixin
 
-**GOOD:**
-```dart
-f(String s) { ... }
-```
-
 **BAD:**
 ```dart
 f(_Private p) { ... }
 class _Private {}
 ```
 
+**GOOD:**
+```dart
+f(String s) { ... }
+```
+
 ''';
 
-class LibraryPrivateTypesInPublicAPI extends LintRule implements NodeLintRule {
+class LibraryPrivateTypesInPublicAPI extends LintRule {
+  static const LintCode code = LintCode('library_private_types_in_public_api',
+      'Invalid use of a private type in a public API.',
+      correctionMessage:
+          'Try making the private type public, or making the API that uses the '
+          'private type also be private.');
+
   LibraryPrivateTypesInPublicAPI()
       : super(
             name: 'library_private_types_in_public_api',
@@ -49,6 +55,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = Visitor(this);
@@ -63,7 +72,7 @@
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.typeParameters?.accept(this);
@@ -72,7 +81,7 @@
 
   @override
   void visitClassTypeAlias(ClassTypeAlias node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.superclass.accept(this);
@@ -82,7 +91,7 @@
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
     var name = node.name;
-    if (name != null && Identifier.isPrivateName(name.name)) {
+    if (name != null && Identifier.isPrivateName(name.lexeme)) {
       return;
     }
     node.parameters.accept(this);
@@ -94,9 +103,18 @@
   }
 
   @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
+      return;
+    }
+    node.typeParameters?.accept(this);
+    node.members.accept(this);
+  }
+
+  @override
   void visitExtensionDeclaration(ExtensionDeclaration node) {
     var name = node.name;
-    if (name == null || Identifier.isPrivateName(name.name)) {
+    if (name == null || Identifier.isPrivateName(name.lexeme)) {
       return;
     }
     node.extendedType.accept(this);
@@ -107,17 +125,31 @@
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
     if (node.fields.variables
-        .any((field) => !Identifier.isPrivateName(field.name.name))) {
+        .any((field) => !Identifier.isPrivateName(field.name.lexeme))) {
       node.fields.type?.accept(this);
     }
   }
 
   @override
   void visitFieldFormalParameter(FieldFormalParameter node) {
-    if (node.isNamed && Identifier.isPrivateName(node.identifier.name)) {
+    if (node.isNamed && Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
-    node.type?.accept(this);
+    // Check for a declared type.
+    var type = node.type;
+    if (type != null) {
+      type.accept(this);
+      return;
+    }
+
+    // Check implicit type.
+    var element = node.declaredElement;
+    if (element is FieldFormalParameterElement) {
+      var type = element.type;
+      if (type is InterfaceType && isPrivateName(type.element.name)) {
+        rule.reportLintForToken(node.name);
+      }
+    }
   }
 
   @override
@@ -127,7 +159,7 @@
 
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.returnType?.accept(this);
@@ -137,7 +169,7 @@
 
   @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.returnType?.accept(this);
@@ -147,7 +179,7 @@
 
   @override
   void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
-    if (node.isNamed && Identifier.isPrivateName(node.identifier.name)) {
+    if (node.isNamed && Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.returnType?.accept(this);
@@ -164,7 +196,7 @@
 
   @override
   void visitGenericTypeAlias(GenericTypeAlias node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.typeParameters?.accept(this);
@@ -173,7 +205,7 @@
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.returnType?.accept(this);
@@ -183,7 +215,7 @@
 
   @override
   void visitMixinDeclaration(MixinDeclaration node) {
-    if (Identifier.isPrivateName(node.name.name)) {
+    if (Identifier.isPrivateName(node.name.lexeme)) {
       return;
     }
     node.onClause?.superclassConstraints.accept(this);
@@ -192,18 +224,50 @@
   }
 
   @override
+  void visitNamedType(NamedType node) {
+    var element = node.name.staticElement;
+    if (element != null && isPrivate(element)) {
+      rule.reportLint(node.name);
+    }
+    node.typeArguments?.accept(this);
+  }
+
+  @override
   void visitSimpleFormalParameter(SimpleFormalParameter node) {
-    var name = node.identifier;
-    if (name != null && node.isNamed && Identifier.isPrivateName(name.name)) {
+    var name = node.name;
+    if (name != null && node.isNamed && Identifier.isPrivateName(name.lexeme)) {
       return;
     }
     node.type?.accept(this);
   }
 
   @override
+  void visitSuperFormalParameter(SuperFormalParameter node) {
+    if (node.isNamed && Identifier.isPrivateName(node.name.lexeme)) {
+      return;
+    }
+
+    // Check for a declared type.
+    var type = node.type;
+    if (type != null) {
+      type.accept(this);
+      return;
+    }
+
+    // Check implicit type.
+    var element = node.declaredElement;
+    if (element is SuperFormalParameterElement) {
+      var type = element.type;
+      if (type is InterfaceType && isPrivateName(type.element.name)) {
+        rule.reportLintForToken(node.name);
+      }
+    }
+  }
+
+  @override
   void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
     if (node.variables.variables
-        .any((field) => !Identifier.isPrivateName(field.name.name))) {
+        .any((field) => !Identifier.isPrivateName(field.name.lexeme))) {
       node.variables.type?.accept(this);
     }
   }
@@ -214,15 +278,6 @@
   }
 
   @override
-  void visitTypeName(TypeName node) {
-    var element = node.name.staticElement;
-    if (element != null && isPrivate(element)) {
-      rule.reportLint(node.name);
-    }
-    node.typeArguments?.accept(this);
-  }
-
-  @override
   void visitTypeParameter(TypeParameter node) {
     node.bound?.accept(this);
   }
@@ -234,10 +289,10 @@
 
   /// Return `true` if the given [element] is private or is defined in a private
   /// library.
-  static bool isPrivate(Element element) {
-    var name = element.name;
-    return name != null && Identifier.isPrivateName(name);
-  }
+  static bool isPrivate(Element element) => isPrivateName(element.name);
+
+  static bool isPrivateName(String? name) =>
+      name != null && Identifier.isPrivateName(name);
 }
 
 class Visitor extends SimpleAstVisitor {
diff --git a/lib/src/rules/lines_longer_than_80_chars.dart b/lib/src/rules/lines_longer_than_80_chars.dart
index dea433b..2a76e69 100644
--- a/lib/src/rules/lines_longer_than_80_chars.dart
+++ b/lib/src/rules/lines_longer_than_80_chars.dart
@@ -13,7 +13,6 @@
 const _desc = r'Avoid lines longer than 80 characters.';
 
 const _details = r'''
-
 **AVOID** lines longer than 80 characters
 
 Readability studies show that long lines of text are harder to read because your
@@ -42,7 +41,7 @@
 final _uriRegExp = RegExp(r'[/\\]');
 bool _looksLikeUriOrPath(String value) => _uriRegExp.hasMatch(value);
 
-class LinesLongerThan80Chars extends LintRule implements NodeLintRule {
+class LinesLongerThan80Chars extends LintRule {
   LinesLongerThan80Chars()
       : super(
             name: 'lines_longer_than_80_chars',
@@ -74,12 +73,14 @@
     }
   }
 
-  Iterable<Token> _getPrecedingComments(Token token) sync* {
+  Iterable<Token> _getPrecedingComments(Token token) {
+    var tokens = <Token>[];
     Token? comment = token.precedingComments;
     while (comment != null) {
-      yield comment;
+      tokens.add(comment);
       comment = comment.next;
     }
+    return tokens;
   }
 
   void _visitComment(Token comment) {
@@ -176,9 +177,6 @@
   @override
   void visitCompilationUnit(CompilationUnit node) {
     var lineInfo = node.lineInfo;
-    if (lineInfo == null) {
-      return;
-    }
     var lineCount = lineInfo.lineCount;
     var longLines = <_LineInfo>[];
     for (var i = 0; i < lineCount; i++) {
diff --git a/lib/src/rules/list_remove_unrelated_type.dart b/lib/src/rules/list_remove_unrelated_type.dart
index 00e5a81..db7c996 100644
--- a/lib/src/rules/list_remove_unrelated_type.dart
+++ b/lib/src/rules/list_remove_unrelated_type.dart
@@ -3,13 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
 import '../util/unrelated_types_visitor.dart';
 
 const _desc = r'Invocation of `remove` with references of unrelated types.';
 
 const _details = r'''
-
 **DON'T** invoke `remove` on `List` with an instance of different type than
 the parameter type.
 
@@ -119,7 +117,10 @@
 
 ''';
 
-class ListRemoveUnrelatedType extends LintRule implements NodeLintRule {
+class ListRemoveUnrelatedType extends LintRule {
+  static const LintCode code = LintCode('list_remove_unrelated_type',
+      "The argument type '{0}' isn't related to '{1}'.");
+
   ListRemoveUnrelatedType()
       : super(
             name: 'list_remove_unrelated_type',
@@ -128,21 +129,25 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this, context.typeSystem);
+    var visitor = _Visitor(this, context.typeSystem, context.typeProvider);
     registry.addMethodInvocation(this, visitor);
   }
 }
 
 class _Visitor extends UnrelatedTypesProcessors {
-  static final _definition = InterfaceTypeDefinition('List', 'dart.core');
-
-  _Visitor(LintRule rule, TypeSystem typeSystem) : super(rule, typeSystem);
+  _Visitor(super.rule, super.typeSystem, super.typeProvider);
 
   @override
-  InterfaceTypeDefinition get definition => _definition;
-
-  @override
-  String get methodName => 'remove';
+  List<MethodDefinition> get methods => [
+        MethodDefinitionForElement(
+          typeProvider.listElement,
+          'remove',
+          ExpectedArgumentKind.assignableToCollectionTypeArgument,
+        ),
+      ];
 }
diff --git a/lib/src/rules/literal_only_boolean_expressions.dart b/lib/src/rules/literal_only_boolean_expressions.dart
index df16d96..063506d 100644
--- a/lib/src/rules/literal_only_boolean_expressions.dart
+++ b/lib/src/rules/literal_only_boolean_expressions.dart
@@ -11,7 +11,6 @@
 const _desc = r'Boolean expression composed only with literals.';
 
 const _details = r'''
-
 **DON'T** test for conditions composed only by literals, since the value can be
 inferred at compile time.
 
@@ -79,6 +78,17 @@
 }
 ```
 
+**NOTE:** that an exception is made for the common `while (true) { }` idiom,
+which is often reasonably preferred to the equivalent `for (;;)`.
+
+**GOOD:**
+```dart
+void good() {
+  while (true) {
+    // Do stuff.
+  }
+}
+```
 ''';
 
 bool _onlyLiterals(Expression? rawExpression) {
@@ -99,7 +109,11 @@
   return false;
 }
 
-class LiteralOnlyBooleanExpressions extends LintRule implements NodeLintRule {
+class LiteralOnlyBooleanExpressions extends LintRule {
+  static const LintCode code = LintCode('literal_only_boolean_expressions',
+      'The Boolean expression has a constant value.',
+      correctionMessage: 'Try changing the expression.');
+
   LiteralOnlyBooleanExpressions()
       : super(
             name: 'literal_only_boolean_expressions',
@@ -108,6 +122,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -149,7 +166,14 @@
 
   @override
   void visitWhileStatement(WhileStatement node) {
-    if (_onlyLiterals(node.condition)) {
+    var condition = node.condition;
+    // Allow `while (true) { }`
+    // See: https://github.com/dart-lang/linter/issues/453
+    if (condition is BooleanLiteral && condition.value) {
+      return;
+    }
+
+    if (_onlyLiterals(condition)) {
       rule.reportLint(node);
     }
   }
diff --git a/lib/src/rules/missing_whitespace_between_adjacent_strings.dart b/lib/src/rules/missing_whitespace_between_adjacent_strings.dart
index 56275f5..5a6bdd0 100644
--- a/lib/src/rules/missing_whitespace_between_adjacent_strings.dart
+++ b/lib/src/rules/missing_whitespace_between_adjacent_strings.dart
@@ -10,11 +10,10 @@
 const _desc = r'Missing whitespace between adjacent strings.';
 
 const _details = r'''
-
 Add a trailing whitespace to prevent missing whitespace between adjacent
 strings.
 
-With long text split accross adjacent strings it's easy to forget a whitespace
+With long text split across adjacent strings it's easy to forget a whitespace
 between strings.
 
 **BAD:**
@@ -33,8 +32,7 @@
 
 ''';
 
-class MissingWhitespaceBetweenAdjacentStrings extends LintRule
-    implements NodeLintRule {
+class MissingWhitespaceBetweenAdjacentStrings extends LintRule {
   MissingWhitespaceBetweenAdjacentStrings()
       : super(
             name: 'missing_whitespace_between_adjacent_strings',
@@ -46,11 +44,11 @@
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
-    registry.addCompilationUnit(this, visitor);
+    registry.addAdjacentStrings(this, visitor);
   }
 }
 
-class _Visitor extends RecursiveAstVisitor<void> {
+class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
   _Visitor(this.rule);
@@ -128,11 +126,11 @@
   /// Returns whether this contains whitespace, where any
   /// [InterpolationExpression] does not count as whitespace.
   bool get hasWhitespace {
-    if (this is SimpleStringLiteral) {
-      return (this as SimpleStringLiteral).value.hasWhitespace;
-    } else if (this is StringInterpolation) {
-      return (this as StringInterpolation)
-          .elements
+    var self = this;
+    if (self is SimpleStringLiteral) {
+      return self.value.hasWhitespace;
+    } else if (self is StringInterpolation) {
+      return self.elements
           .any((e) => e is InterpolationString && e.value.hasWhitespace);
     }
     return false;
diff --git a/lib/src/rules/no_adjacent_strings_in_list.dart b/lib/src/rules/no_adjacent_strings_in_list.dart
index eab3b26..23e4d71 100644
--- a/lib/src/rules/no_adjacent_strings_in_list.dart
+++ b/lib/src/rules/no_adjacent_strings_in_list.dart
@@ -10,20 +10,10 @@
 const _desc = r"Don't use adjacent strings in list.";
 
 const _details = r'''
-
 **DON'T** use adjacent strings in list.
 
 This can be sign of forgotten comma.
 
-**GOOD:**
-```dart
-List<String> list = <String>[
-  'a' +
-  'b',
-  'c',
-];
-```
-
 **BAD:**
 ```dart
 List<String> list = <String>[
@@ -33,9 +23,22 @@
 ];
 ```
 
+**GOOD:**
+```dart
+List<String> list = <String>[
+  'a' +
+  'b',
+  'c',
+];
+```
+
 ''';
 
-class NoAdjacentStringsInList extends LintRule implements NodeLintRule {
+class NoAdjacentStringsInList extends LintRule {
+  static const LintCode code = LintCode('no_adjacent_strings_in_list',
+      "Don't use adjacent strings in a list literal.",
+      correctionMessage: 'Try adding a comma between the strings.');
+
   NoAdjacentStringsInList()
       : super(
             name: 'no_adjacent_strings_in_list',
@@ -44,6 +47,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/no_default_cases.dart b/lib/src/rules/no_default_cases.dart
index aba4a9d..f4ca432 100644
--- a/lib/src/rules/no_default_cases.dart
+++ b/lib/src/rules/no_default_cases.dart
@@ -8,7 +8,7 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'No default cases.';
 
@@ -22,6 +22,18 @@
 
 **DO** define default behavior outside switch statements.
 
+**BAD:**
+```dart
+  switch (testEnum) {
+    case TestEnum.A:
+      return '123';
+    case TestEnum.B:
+      return 'abc';
+    default:
+      return null;
+  }
+```
+
 **GOOD:**
 ```dart
   switch (testEnum) {
@@ -34,20 +46,9 @@
   return null;
 ```
 
-**BAD:**
-```dart
-  switch (testEnum) {
-    case TestEnum.A:
-      return '123';
-    case TestEnum.B:
-      return 'abc';
-    default:
-      return null;
-  }
-```
 ''';
 
-class NoDefaultCases extends LintRule implements NodeLintRule {
+class NoDefaultCases extends LintRule {
   NoDefaultCases()
       : super(
             name: 'no_default_cases',
@@ -69,17 +70,16 @@
 
   _Visitor(this.rule);
 
-  bool isEnumLikeClass(ClassElement classElement) =>
-      DartTypeUtilities.asEnumLikeClass(classElement) != null;
-
   @override
   void visitSwitchStatement(SwitchStatement statement) {
     var expressionType = statement.expression.staticType;
     if (expressionType is InterfaceType) {
       for (var member in statement.members) {
         if (member is SwitchDefault) {
-          var classElement = expressionType.element;
-          if (classElement.isEnum || isEnumLikeClass(classElement)) {
+          var interfaceElement = expressionType.element;
+          if (interfaceElement is EnumElement ||
+              interfaceElement is ClassElement &&
+                  interfaceElement.isEnumLikeClass) {
             rule.reportLint(member);
           }
           return;
diff --git a/lib/src/rules/no_duplicate_case_values.dart b/lib/src/rules/no_duplicate_case_values.dart
index fbe8ea8..5fcbcbf 100644
--- a/lib/src/rules/no_duplicate_case_values.dart
+++ b/lib/src/rules/no_duplicate_case_values.dart
@@ -11,20 +11,10 @@
 const _desc = r"Don't use more than one case with same value.";
 
 const _details = r'''
-
 **DON'T** use more than one case with same value.
 
 This is usually a typo or changed value of constant.
 
-**GOOD:**
-```dart
-const int A = 1;
-switch (v) {
-  case A:
-  case 2:
-}
-```
-
 **BAD:**
 ```dart
 const int A = 1;
@@ -36,12 +26,24 @@
 }
 ```
 
+**GOOD:**
+```dart
+const int A = 1;
+switch (v) {
+  case A:
+  case 2:
+}
+```
+
 ''';
 
-String message(String value1, String value2) =>
-    'Do not use more than one case with same value ($value1 and $value2)';
+class NoDuplicateCaseValues extends LintRule {
+  static const LintCode code = LintCode(
+      'no_duplicate_case_values',
+      "The value of the case clause ('{0}') is equal to the value of an "
+          "earlier case clause ('{1}').",
+      correctionMessage: 'Try removing or changing the value.');
 
-class NoDuplicateCaseValues extends LintRule implements NodeLintRule {
   NoDuplicateCaseValues()
       : super(
             name: 'no_duplicate_case_values',
@@ -50,24 +52,14 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
     registry.addSwitchStatement(this, visitor);
   }
-
-  void reportLintWithDescription(AstNode node, String description) {
-    reporter.reportErrorForNode(_LintCode(name, description), node, []);
-  }
-}
-
-class _LintCode extends LintCode {
-  static final registry = <String, _LintCode>{};
-
-  factory _LintCode(String name, String message) =>
-      registry.putIfAbsent(name + message, () => _LintCode._(name, message));
-
-  _LintCode._(String name, String message) : super(name, message);
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
@@ -93,9 +85,11 @@
         }
 
         var duplicateValue = values[value];
+        // TODO(brianwilkeson) This would benefit from having a context message
+        //  pointing at the `duplicateValue`.
         if (duplicateValue != null) {
-          rule.reportLintWithDescription(member,
-              message(duplicateValue.toString(), expression.toString()));
+          rule.reportLint(expression,
+              arguments: [expression.toString(), duplicateValue.toString()]);
         } else {
           values[value] = expression;
         }
diff --git a/lib/src/rules/no_leading_underscores_for_library_prefixes.dart b/lib/src/rules/no_leading_underscores_for_library_prefixes.dart
new file mode 100644
index 0000000..b35b879
--- /dev/null
+++ b/lib/src/rules/no_leading_underscores_for_library_prefixes.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+import '../utils.dart';
+
+const _desc = r'Avoid leading underscores for library prefixes.';
+
+const _details = r'''
+**DON'T** use a leading underscore for library prefixes.
+There is no concept of "private" for library prefixes. When one of those has a
+name that starts with an underscore, it sends a confusing signal to the reader. 
+To avoid that, don't use leading underscores in those names.
+
+**BAD:**
+```dart
+import 'dart:core' as _core;
+```
+
+**GOOD:**
+```dart
+import 'dart:core' as core;
+```
+''';
+
+class NoLeadingUnderscoresForLibraryPrefixes extends LintRule {
+  static const LintCode code = LintCode(
+      'no_leading_underscores_for_library_prefixes',
+      "The library prefix '{0}' starts with an underscore.",
+      correctionMessage:
+          'Try renaming the prefix to not start with an underscore.');
+
+  NoLeadingUnderscoresForLibraryPrefixes()
+      : super(
+            name: 'no_leading_underscores_for_library_prefixes',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addImportDirective(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  void checkIdentifier(SimpleIdentifier? id) {
+    if (id == null) {
+      return;
+    }
+
+    if (hasLeadingUnderscore(id.name)) {
+      rule.reportLint(id, arguments: [id.name]);
+    }
+  }
+
+  @override
+  void visitImportDirective(ImportDirective node) {
+    checkIdentifier(node.prefix);
+  }
+}
diff --git a/lib/src/rules/no_leading_underscores_for_local_identifiers.dart b/lib/src/rules/no_leading_underscores_for_local_identifiers.dart
new file mode 100644
index 0000000..3b0bfe3
--- /dev/null
+++ b/lib/src/rules/no_leading_underscores_for_local_identifiers.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+import '../util/ascii_utils.dart';
+import '../utils.dart';
+
+const _desc = r'Avoid leading underscores for local identifiers.';
+
+const _details = r'''
+**DON'T** use a leading underscore for identifiers that aren't private. Dart
+uses a leading underscore in an identifier to mark members and top-level
+declarations as private. This trains users to associate a leading underscore
+with one of those kinds of declarations. They see `_` and  think "private".
+There is no concept of "private" for local variables or parameters.  When one of 
+those has a name that starts with an underscore, it sends a confusing signal to
+the reader. To avoid that, don't use leading underscores in those names.
+
+**EXCEPTION:**: An unused parameter can be named `_`, `__`, `___`, etc.  This is
+common practice in callbacks where you are passed a value but you don't need
+to use it. Giving it a name that consists solely of underscores is the idiomatic
+way to indicate that the value isn't used.
+
+**BAD:**
+```dart
+void print(String _name) {
+  var _size = _name.length;
+  ...
+}
+```
+**GOOD:**
+
+```dart
+void print(String name) {
+  var size = name.length;
+  ...
+}
+```
+
+**OK:**
+
+```dart
+[1,2,3].map((_) => print('Hello'));
+```
+''';
+
+class NoLeadingUnderscoresForLocalIdentifiers extends LintRule {
+  static const LintCode code = LintCode(
+      'no_leading_underscores_for_local_identifiers',
+      "The local variable '{0}' starts with an underscore.",
+      correctionMessage:
+          'Try renaming the variable to not start with an underscore.');
+
+  NoLeadingUnderscoresForLocalIdentifiers()
+      : super(
+            name: 'no_leading_underscores_for_local_identifiers',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addCatchClause(this, visitor);
+    registry.addDeclaredIdentifier(this, visitor);
+    registry.addFormalParameterList(this, visitor);
+    registry.addForPartsWithDeclarations(this, visitor);
+    registry.addFunctionDeclarationStatement(this, visitor);
+    registry.addVariableDeclarationStatement(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  void checkIdentifier(Token? id) {
+    if (id == null) return;
+    if (!hasLeadingUnderscore(id.lexeme)) return;
+    if (id.lexeme.isJustUnderscores) return;
+
+    rule.reportLintForToken(id, arguments: [id.lexeme]);
+  }
+
+  @override
+  void visitCatchClause(CatchClause node) {
+    checkIdentifier(node.exceptionParameter?.name);
+    checkIdentifier(node.stackTraceParameter?.name);
+  }
+
+  @override
+  void visitDeclaredIdentifier(DeclaredIdentifier node) {
+    checkIdentifier(node.name);
+  }
+
+  @override
+  void visitFormalParameterList(FormalParameterList node) {
+    for (var parameter in node.parameters) {
+      if (parameter is DefaultFormalParameter) {
+        parameter = parameter.parameter;
+      }
+      if (parameter is FieldFormalParameter ||
+          parameter is SuperFormalParameter) {
+        // These are not local identifiers.
+        return;
+      }
+      if (!parameter.isNamed) {
+        // Named parameters produce a `private_optional_parameter` diagnostic.
+        checkIdentifier(parameter.name);
+      }
+    }
+  }
+
+  @override
+  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
+    for (var variable in node.variables.variables) {
+      checkIdentifier(variable.name);
+    }
+  }
+
+  @override
+  void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
+    checkIdentifier(node.functionDeclaration.name);
+  }
+
+  @override
+  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+    for (var variable in node.variables.variables) {
+      checkIdentifier(variable.name);
+    }
+  }
+}
diff --git a/lib/src/rules/no_logic_in_create_state.dart b/lib/src/rules/no_logic_in_create_state.dart
index 9375509..540df58 100644
--- a/lib/src/rules/no_logic_in_create_state.dart
+++ b/lib/src/rules/no_logic_in_create_state.dart
@@ -58,7 +58,11 @@
 ```
 ''';
 
-class NoLogicInCreateState extends LintRule implements NodeLintRule {
+class NoLogicInCreateState extends LintRule {
+  static const LintCode code = LintCode(
+      'no_logic_in_create_state', "Don't put any logic in 'createState'.",
+      correctionMessage: "Try moving the logic out of 'createState'.");
+
   NoLogicInCreateState()
       : super(
             name: 'no_logic_in_create_state',
@@ -67,6 +71,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -81,7 +88,7 @@
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    if (node.name.name != 'createState') {
+    if (node.name.lexeme != 'createState') {
       return;
     }
 
@@ -95,7 +102,7 @@
     if (body is BlockFunctionBody) {
       var statements = body.block.statements;
       if (statements.length == 1) {
-        var statement = statements[0];
+        var statement = statements.first;
         if (statement is ReturnStatement) {
           expressionToTest = statement.expression;
         }
diff --git a/lib/src/rules/no_runtimeType_toString.dart b/lib/src/rules/no_runtimeType_toString.dart
index 1a7fa0f..9d64705 100644
--- a/lib/src/rules/no_runtimeType_toString.dart
+++ b/lib/src/rules/no_runtimeType_toString.dart
@@ -6,13 +6,13 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
 
 const _desc = r'Avoid calling toString() on runtimeType.';
 
 const _details = r'''
-
 Calling `toString` on a runtime type is a non-trivial operation that can
 negatively impact performance. It's better to avoid it.
 
@@ -41,7 +41,7 @@
 
 ''';
 
-class NoRuntimeTypeToString extends LintRule implements NodeLintRule {
+class NoRuntimeTypeToString extends LintRule {
   NoRuntimeTypeToString()
       : super(
             name: 'no_runtimeType_toString',
@@ -99,13 +99,16 @@
         if (n is ThrowExpression) return true;
         if (n is CatchClause) return true;
         if (n is MixinDeclaration) return true;
-        if (n is ClassDeclaration && n.isAbstract) return true;
+        if (n is ClassDeclaration && n.abstractKeyword != null) return true;
         if (n is ExtensionDeclaration) {
           var declaredElement = n.declaredElement;
           if (declaredElement != null) {
-            var extendedElement = declaredElement.extendedType.element;
-            return !(extendedElement is ClassElement &&
-                !extendedElement.isAbstract);
+            var extendedType = declaredElement.extendedType;
+            if (extendedType is InterfaceType) {
+              var extendedElement = extendedType.element;
+              return !(extendedElement is ClassElement &&
+                  !extendedElement.isAbstract);
+            }
           }
         }
         return false;
diff --git a/lib/src/rules/non_constant_identifier_names.dart b/lib/src/rules/non_constant_identifier_names.dart
index e5d2a9a..6725f63 100644
--- a/lib/src/rules/non_constant_identifier_names.dart
+++ b/lib/src/rules/non_constant_identifier_names.dart
@@ -3,15 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
+import '../util/ascii_utils.dart';
 import '../utils.dart';
 
 const _desc = r'Name non-constant identifiers using lowerCamelCase.';
 
 const _details = r'''
-
 **DO** name non-constant identifiers using lowerCamelCase.
 
 Class members, top-level definitions, variables, parameters, named parameters
@@ -31,7 +32,12 @@
 
 ''';
 
-class NonConstantIdentifierNames extends LintRule implements NodeLintRule {
+class NonConstantIdentifierNames extends LintRule {
+  static const LintCode code = LintCode('non_constant_identifier_names',
+      "The variable name '{0}' isn't a lowerCamelCase identifier.",
+      correctionMessage:
+          'Try changing the name to follow the lowerCamelCase style.');
+
   NonConstantIdentifierNames()
       : super(
             name: 'non_constant_identifier_names',
@@ -40,6 +46,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -49,6 +58,8 @@
     registry.addFormalParameterList(this, visitor);
     registry.addFunctionDeclaration(this, visitor);
     registry.addMethodDeclaration(this, visitor);
+    registry.addRecordLiteral(this, visitor);
+    registry.addRecordTypeAnnotation(this, visitor);
     registry.addVariableDeclaration(this, visitor);
     registry.addVariableDeclarationStatement(this, visitor);
   }
@@ -59,22 +70,23 @@
 
   _Visitor(this.rule);
 
-  void checkIdentifier(SimpleIdentifier? id, {bool underscoresOk = false}) {
+  void checkIdentifier(Token? id, {bool underscoresOk = false}) {
     if (id == null) {
       return;
     }
-    if (underscoresOk && isJustUnderscores(id.name)) {
+    var name = id.lexeme;
+    if (underscoresOk && name.isJustUnderscores) {
       // For example, `___` is OK in a callback.
       return;
     }
-    if (!isLowerCamelCase(id.name)) {
-      rule.reportLint(id);
+    if (!isLowerCamelCase(name)) {
+      rule.reportLintForToken(id, arguments: [name]);
     }
   }
 
   @override
   void visitCatchClause(CatchClause node) {
-    checkIdentifier(node.exceptionParameter, underscoresOk: true);
+    checkIdentifier(node.exceptionParameter?.name, underscoresOk: true);
   }
 
   @override
@@ -86,14 +98,14 @@
 
   @override
   void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
-    checkIdentifier(node.loopVariable.identifier);
+    checkIdentifier(node.loopVariable.name);
   }
 
   @override
   void visitFormalParameterList(FormalParameterList node) {
     for (var p in node.parameters) {
       if (p is! FieldFormalParameter) {
-        checkIdentifier(p.identifier, underscoresOk: true);
+        checkIdentifier(p.name, underscoresOk: true);
       }
     }
   }
@@ -111,6 +123,29 @@
   }
 
   @override
+  void visitRecordLiteral(RecordLiteral node) {
+    for (var fieldExpression in node.fields) {
+      if (fieldExpression is NamedExpression) {
+        checkIdentifier(fieldExpression.name.label.token);
+      }
+    }
+  }
+
+  @override
+  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
+    var positionalFields = node.positionalFields;
+    for (var field in positionalFields) {
+      checkIdentifier(field.name);
+    }
+
+    var namedFields = node.namedFields;
+    if (namedFields == null) return;
+    for (var field in namedFields.fields) {
+      checkIdentifier(field.name);
+    }
+  }
+
+  @override
   void visitVariableDeclaration(VariableDeclaration node) {
     if (!node.isConst) {
       checkIdentifier(node.name);
diff --git a/lib/src/rules/noop_primitive_operations.dart b/lib/src/rules/noop_primitive_operations.dart
index 664ece7..2d01840 100644
--- a/lib/src/rules/noop_primitive_operations.dart
+++ b/lib/src/rules/noop_primitive_operations.dart
@@ -6,11 +6,11 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
+import '../ast.dart';
 
 const _desc = r'Noop primitive operations.';
 
 const _details = r'''
-
 Some operations on primitive types are idempotent and can be removed.
 
 **BAD:**
@@ -28,10 +28,12 @@
 string = 'hello\n'
     'world\n'
     ''; // useless empty string
+
+'string with ${x.toString()}';
 ```
 ''';
 
-class NoopPrimitiveOperations extends LintRule implements NodeLintRule {
+class NoopPrimitiveOperations extends LintRule {
   NoopPrimitiveOperations()
       : super(
           name: 'noop_primitive_operations',
@@ -47,6 +49,7 @@
   ) {
     var visitor = _Visitor(this, context);
     registry.addAdjacentStrings(this, visitor);
+    registry.addInterpolationExpression(this, visitor);
     registry.addMethodInvocation(this, visitor);
   }
 }
@@ -67,9 +70,21 @@
   }
 
   @override
+  void visitInterpolationExpression(InterpolationExpression node) {
+    _checkToStringInvocation(node.expression);
+  }
+
+  @override
   void visitMethodInvocation(MethodInvocation node) {
     var type = node.realTarget?.staticType;
-    if (type == null) return;
+    if (type == null) {
+      // print(xxx.toString())
+      if (node.methodName.staticElement.isDartCorePrint &&
+          node.argumentList.arguments.length == 1) {
+        _checkToStringInvocation(node.argumentList.arguments.first);
+      }
+      return;
+    }
 
     // string.toString()
     if (type.isDartCoreString &&
@@ -93,4 +108,14 @@
       return;
     }
   }
+
+  void _checkToStringInvocation(Expression expression) {
+    if (expression is MethodInvocation &&
+        expression.realTarget != null &&
+        expression.realTarget is! SuperExpression &&
+        expression.methodName.name == 'toString' &&
+        expression.argumentList.arguments.isEmpty) {
+      rule.reportLint(expression.methodName);
+    }
+  }
 }
diff --git a/lib/src/rules/null_check_on_nullable_type_parameter.dart b/lib/src/rules/null_check_on_nullable_type_parameter.dart
index d99fae0..cd2adca 100644
--- a/lib/src/rules/null_check_on_nullable_type_parameter.dart
+++ b/lib/src/rules/null_check_on_nullable_type_parameter.dart
@@ -14,8 +14,7 @@
 const _desc = r"Don't use null check on a potentially nullable type parameter.";
 
 const _details = r'''
-
-Don't use null check on a potentially nullable type parameter.
+**DON'T** use null check on a potentially nullable type parameter.
 
 Given a generic type parameter `T` which has a nullable bound (e.g. the default
 bound of `Object?`), it is very easy to introduce erroneous null checks when
@@ -44,18 +43,26 @@
 
 ''';
 
-class NullCheckOnNullableTypeParameter extends LintRule
-    implements NodeLintRule {
+class NullCheckOnNullableTypeParameter extends LintRule {
+  static const LintCode code = LintCode(
+      'null_check_on_nullable_type_parameter',
+      "The null check operator shouldn't be used on a variable whose type is a "
+          'potentially nullable type parameter.',
+      correctionMessage: "Try explicitly testing for 'null'.");
+
   NullCheckOnNullableTypeParameter()
       : super(
           name: 'null_check_on_nullable_type_parameter',
           description: _desc,
           details: _details,
-          maturity: Maturity.experimental,
+          maturity: Maturity.stable,
           group: Group.style,
         );
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     if (!context.isEnabled(Feature.non_nullable)) {
@@ -63,16 +70,15 @@
     }
 
     var visitor = _Visitor(this, context);
-    registry.addCompilationUnit(this, visitor);
     registry.addPostfixExpression(this, visitor);
   }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  _Visitor(this.rule, this.context);
-
   final LintRule rule;
+
   final LinterContext context;
+  _Visitor(this.rule, this.context);
 
   @override
   void visitPostfixExpression(PostfixExpression node) {
diff --git a/lib/src/rules/null_closures.dart b/lib/src/rules/null_closures.dart
index b998ee4..8daadc4 100644
--- a/lib/src/rules/null_closures.dart
+++ b/lib/src/rules/null_closures.dart
@@ -6,16 +6,14 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/src/generated/utilities_general.dart'; // ignore: implementation_imports
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Do not pass `null` as an argument where a closure is expected.';
 
 const _details = r'''
-
-**DO NOT** pass null as an argument where a closure is expected.
+**DON'T** pass `null` as an argument where a closure is expected.
 
 Often a closure that is passed to a method will only be called conditionally,
 so that tests and "happy path" production calls do not reveal that `null` will
@@ -185,7 +183,7 @@
 
 /// Function with closure parameters that cannot accept null arguments.
 class NonNullableFunction {
-  final String? library;
+  final String library;
   final String? type;
   final String? name;
   final List<int> positional;
@@ -196,7 +194,7 @@
 
   @override
   int get hashCode =>
-      JenkinsSmiHash.hash3(library.hashCode, type.hashCode, name.hashCode);
+      Object.hash(library.hashCode, type.hashCode, name.hashCode);
 
   /// Two [NonNullableFunction] objects are equal if their [library], [type],
   /// and [name] are equal, for the purpose of discovering whether a function
@@ -206,7 +204,11 @@
       other is NonNullableFunction && other.hashCode == hashCode;
 }
 
-class NullClosures extends LintRule implements NodeLintRule {
+class NullClosures extends LintRule {
+  static const LintCode code = LintCode(
+      'null_closures', "Closure can't be 'null' because it might be invoked.",
+      correctionMessage: 'Try providing a non-null closure.');
+
   NullClosures()
       : super(
             name: 'null_closures',
@@ -215,6 +217,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -234,8 +239,7 @@
     var type = node.staticType;
     for (var constructor in _constructorsWithNonNullableArguments) {
       if (constructorName.name?.name == constructor.name) {
-        if (DartTypeUtilities.extendsClass(
-            type, constructor.type, constructor.library)) {
+        if (type.extendsClass(constructor.type, constructor.library)) {
           _checkNullArgForClosure(
               node.argumentList, constructor.positional, constructor.named);
         }
@@ -299,22 +303,23 @@
       return null;
     }
 
-    NonNullableFunction? getMethod(String? library, String? className) =>
+    NonNullableFunction? getMethod(String library, String className) =>
         possibleMethods
             .lookup(NonNullableFunction(library, className, methodName));
 
-    var method = getMethod(type.element.library.name, type.element.name);
-    if (method != null) {
-      return method;
-    }
-
     var element = type.element;
     if (element.isSynthetic) {
       return null;
     }
+
+    var method = getMethod(element.library.name, element.name);
+    if (method != null) {
+      return method;
+    }
+
     for (var supertype in element.allSupertypes) {
-      method =
-          getMethod(supertype.element.library.name, supertype.element.name);
+      var superElement = supertype.element;
+      method = getMethod(superElement.library.name, superElement.name);
       if (method != null) {
         return method;
       }
diff --git a/lib/src/rules/omit_local_variable_types.dart b/lib/src/rules/omit_local_variable_types.dart
index 7158993..66cfa1f 100644
--- a/lib/src/rules/omit_local_variable_types.dart
+++ b/lib/src/rules/omit_local_variable_types.dart
@@ -4,47 +4,66 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Omit type annotations for local variables.';
 
 const _details = r'''
+**DON'T** redundantly type annotate initialized local variables.
 
-**CONSIDER** omitting type annotations for local variables.
-
-Usually, the types of local variables can be easily inferred, so it isn't
-necessary to annotate them.
+Local variables, especially in modern code where functions tend to be small,
+have very little scope. Omitting the type focuses the reader's attention on the
+more important *name* of the variable and its initialized value.
 
 **BAD:**
 ```dart
-Map<int, List<Person>> groupByZip(Iterable<Person> people) {
-  Map<int, List<Person>> peopleByZip = <int, List<Person>>{};
-  for (Person person in people) {
-    peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
-    peopleByZip[person.zip].add(person);
+List<List<Ingredient>> possibleDesserts(Set<Ingredient> pantry) {
+  List<List<Ingredient>> desserts = <List<Ingredient>>[];
+  for (final List<Ingredient> recipe in cookbook) {
+    if (pantry.containsAll(recipe)) {
+      desserts.add(recipe);
+    }
   }
-  return peopleByZip;
+
+  return desserts;
 }
 ```
 
 **GOOD:**
 ```dart
-Map<int, List<Person>> groupByZip(Iterable<Person> people) {
-  var peopleByZip = <int, List<Person>>{};
-  for (var person in people) {
-    peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
-    peopleByZip[person.zip].add(person);
+List<List<Ingredient>> possibleDesserts(Set<Ingredient> pantry) {
+  var desserts = <List<Ingredient>>[];
+  for (final recipe in cookbook) {
+    if (pantry.containsAll(recipe)) {
+      desserts.add(recipe);
+    }
   }
-  return peopleByZip;
+
+  return desserts;
 }
 ```
 
+Sometimes the inferred type is not the type you want the variable to have. For
+example, you may intend to assign values of other types later. In that case,
+annotate the variable with the type you want.
+
+**GOOD:**
+```dart
+Widget build(BuildContext context) {
+  [!Widget!] result = Text('You won!');
+  if (applyPadding) {
+    result = Padding(padding: EdgeInsets.all(8.0), child: result);
+  }
+  return result;
+}
+```
 ''';
 
-class OmitLocalVariableTypes extends LintRule implements NodeLintRule {
+class OmitLocalVariableTypes extends LintRule {
   OmitLocalVariableTypes()
       : super(
             name: 'omit_local_variable_types',
@@ -82,10 +101,9 @@
       }
       var iterableType = loopParts.iterable.staticType;
       if (iterableType is InterfaceType) {
-        var iterableInterfaces = DartTypeUtilities.getImplementedInterfaces(
-                iterableType)
-            .where((type) =>
-                DartTypeUtilities.isInterface(type, 'Iterable', 'dart.core'));
+        // TODO(srawlins): Is `DartType.asInstanceOf` the more correct API here?
+        var iterableInterfaces = iterableType.implementedInterfaces
+            .where((type) => type.isDartCoreIterable);
         if (iterableInterfaces.length == 1 &&
             iterableInterfaces.first.typeArguments.first == staticType) {
           rule.reportLint(loopVariableType);
@@ -99,13 +117,33 @@
     _visitVariableDeclarationList(node.variables);
   }
 
+  bool _dependsOnDeclaredTypeForInference(Expression? initializer) {
+    if (initializer is MethodInvocation) {
+      if (initializer.typeArguments == null) {
+        var element = initializer.methodName.staticElement;
+        if (element is FunctionElement) {
+          if (element.returnType is TypeParameterType) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
   void _visitVariableDeclarationList(VariableDeclarationList node) {
     var staticType = node.type?.type;
-    if (staticType == null || staticType.isDynamic) {
+    if (staticType == null ||
+        staticType.isDynamic ||
+        staticType.isDartCoreNull) {
       return;
     }
     for (var child in node.variables) {
-      if (child.initializer?.staticType != staticType) {
+      var initializer = child.initializer;
+      if (initializer?.staticType != staticType) {
+        return;
+      }
+      if (_dependsOnDeclaredTypeForInference(initializer)) {
         return;
       }
     }
diff --git a/lib/src/rules/one_member_abstracts.dart b/lib/src/rules/one_member_abstracts.dart
index 4aae4fa..1e0698a 100644
--- a/lib/src/rules/one_member_abstracts.dart
+++ b/lib/src/rules/one_member_abstracts.dart
@@ -11,7 +11,6 @@
     r'Avoid defining a one-member abstract class when a simple function will do.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **AVOID** defining a one-member abstract class when a simple function will do.
@@ -22,11 +21,6 @@
 with a meaningless name like `call` or `invoke`, there is a good chance
 you just want a function.
 
-**GOOD:**
-```dart
-typedef Predicate = bool Function(item);
-```
-
 **BAD:**
 ```dart
 abstract class Predicate {
@@ -34,9 +28,14 @@
 }
 ```
 
+**GOOD:**
+```dart
+typedef Predicate = bool Function(item);
+```
+
 ''';
 
-class OneMemberAbstracts extends LintRule implements NodeLintRule {
+class OneMemberAbstracts extends LintRule {
   OneMemberAbstracts()
       : super(
             name: 'one_member_abstracts',
@@ -69,15 +68,15 @@
     if (declaredElement.mixins.isNotEmpty) {
       return;
     }
-    if (node.isAbstract &&
+    if (node.abstractKeyword != null &&
         node.extendsClause == null &&
         node.members.length == 1) {
-      var member = node.members[0];
+      var member = node.members.first;
       if (member is MethodDeclaration &&
           member.isAbstract &&
           !member.isGetter &&
           !member.isSetter) {
-        rule.reportLint(node.name);
+        rule.reportLintForToken(node.name);
       }
     }
   }
diff --git a/lib/src/rules/only_throw_errors.dart b/lib/src/rules/only_throw_errors.dart
index dd1f1ef..7785486 100644
--- a/lib/src/rules/only_throw_errors.dart
+++ b/lib/src/rules/only_throw_errors.dart
@@ -9,13 +9,13 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
+import '../extensions.dart';
 import '../util/dart_type_utilities.dart';
 
 const _desc =
     r'Only throw instances of classes extending either Exception or Error.';
 
 const _details = r'''
-
 **DO** throw only instances of classes that extend `dart.core.Error` or
 `dart.core.Exception`.
 
@@ -56,11 +56,10 @@
   var typeForInterfaceCheck = type?.typeForInterfaceCheck;
   return typeForInterfaceCheck == null ||
       typeForInterfaceCheck.isDynamic ||
-      DartTypeUtilities.implementsAnyInterface(
-          typeForInterfaceCheck, _interfaceDefinitions);
+      typeForInterfaceCheck.implementsAnyInterface(_interfaceDefinitions);
 }
 
-class OnlyThrowErrors extends LintRule implements NodeLintRule {
+class OnlyThrowErrors extends LintRule {
   OnlyThrowErrors()
       : super(
             name: 'only_throw_errors',
diff --git a/lib/src/rules/overridden_fields.dart b/lib/src/rules/overridden_fields.dart
index b0694d2..2fed765 100644
--- a/lib/src/rules/overridden_fields.dart
+++ b/lib/src/rules/overridden_fields.dart
@@ -13,7 +13,6 @@
 const _desc = r"Don't override fields.";
 
 const _details = r'''
-
 **DON'T** override fields.
 
 Overriding fields is almost always done unintentionally.  Regardless, it is a
@@ -87,17 +86,23 @@
   return interfaces.where((i) => i != interface);
 }
 
-Iterable<InterfaceType> _findAllSupertypesInMixin(ClassElement classElement) {
+Iterable<InterfaceType> _findAllSupertypesInMixin(MixinElement mixinElement) {
   var supertypes = <InterfaceType>[];
   var accumulator = <InterfaceType>[];
-  for (var type in classElement.superclassConstraints) {
+  for (var type in mixinElement.superclassConstraints) {
     supertypes.add(type);
     supertypes.addAll(_findAllSupertypesAndMixins(type, accumulator));
   }
   return supertypes;
 }
 
-class OverriddenFields extends LintRule implements NodeLintRule {
+class OverriddenFields extends LintRule {
+  static const LintCode code = LintCode(
+      'overridden_fields', "Field overrides a field inherited from '{0}'.",
+      correctionMessage:
+          'Try removing the field, overriding the getter and setter if '
+          'necessary.');
+
   OverriddenFields()
       : super(
             name: 'overridden_fields',
@@ -106,6 +111,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -125,11 +133,12 @@
     }
 
     for (var variable in node.fields.variables) {
-      var declaredElement = variable.declaredElement;
-      if (declaredElement != null) {
-        var field = _getOverriddenMember(declaredElement);
-        if (field != null && !field.isAbstract) {
-          rule.reportLint(variable.name);
+      var declaredField = variable.declaredElement;
+      if (declaredField != null) {
+        var overriddenField = _getOverriddenMember(declaredField);
+        if (overriddenField != null && !overriddenField.isAbstract) {
+          rule.reportLintForToken(variable.name,
+              arguments: [overriddenField.enclosingElement.displayName]);
         }
       }
     }
@@ -139,7 +148,10 @@
     var memberName = member.name;
     var library = member.library;
     bool isOverriddenMember(PropertyAccessorElement a) {
-      if (memberName != null && a.isSynthetic && a.name == memberName) {
+      if (memberName == null || a.isStatic) {
+        return false;
+      }
+      if (a.isSynthetic && a.name == memberName) {
         // Ensure that private members are overriding a member of the same library.
         if (Identifier.isPrivateName(memberName)) {
           return library == a.library;
@@ -152,13 +164,13 @@
     bool containsOverriddenMember(InterfaceType i) =>
         i.accessors.any(isOverriddenMember);
     var enclosingElement = member.enclosingElement;
-    if (enclosingElement is! ClassElement) {
+    if (enclosingElement is! InterfaceElement) {
       return null;
     }
     var classElement = enclosingElement;
 
     Iterable<InterfaceType> interfaces;
-    if (classElement.isMixin) {
+    if (classElement is MixinElement) {
       interfaces = _findAllSupertypesInMixin(classElement);
     } else {
       interfaces =
diff --git a/lib/src/rules/package_api_docs.dart b/lib/src/rules/package_api_docs.dart
index d9a7399..700bd2e 100644
--- a/lib/src/rules/package_api_docs.dart
+++ b/lib/src/rules/package_api_docs.dart
@@ -6,12 +6,10 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../ast.dart';
 
 const _desc = r'Provide doc comments for all public APIs.';
 
 const _details = r'''
-
 **DO** provide doc comments for all public APIs.
 
 As described in the [pub package layout doc](https://dart.dev/tools/pub/package-layout#implementation-files),
@@ -36,6 +34,13 @@
 
 All public API members should be documented with `///` doc-style comments.
 
+**BAD:**
+```dart
+class Bar {
+  void bar();
+}
+```
+
 **GOOD:**
 ```dart
 /// A Foo.
@@ -47,21 +52,12 @@
 }
 ```
 
-**BAD:**
-```dart
-class Bar {
-  void bar();
-}
-```
-
 Advice for writing good doc comments can be found in the
 [Doc Writing Guidelines](https://dart.dev/guides/language/effective-dart/documentation).
 
 ''';
 
-class PackageApiDocs extends LintRule implements ProjectVisitor, NodeLintRule {
-  DartProject? project;
-
+class PackageApiDocs extends LintRule {
   PackageApiDocs()
       : super(
             name: 'package_api_docs',
@@ -70,9 +66,6 @@
             group: Group.style);
 
   @override
-  ProjectVisitor getProjectVisitor() => this;
-
-  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -86,11 +79,6 @@
     registry.addClassTypeAlias(this, visitor);
     registry.addFunctionTypeAlias(this, visitor);
   }
-
-  @override
-  void visit(DartProject project) {
-    this.project = project;
-  }
 }
 
 class _Visitor extends GeneralizingAstVisitor {
@@ -98,20 +86,25 @@
 
   _Visitor(this.rule);
 
+  // ignore: prefer_expression_function_bodies
   void check(Declaration node) {
-    // If no project info is set, bail early.
-    // https://github.com/dart-lang/linter/issues/154
-    var currentProject = rule.project;
-    if (currentProject == null) {
-      return;
-    }
+    // See: https://github.com/dart-lang/linter/issues/3395
+    // (`DartProject` removal).
+    return;
 
-    var declaredElement = node.declaredElement;
-    if (declaredElement != null && currentProject.isApi(declaredElement)) {
-      if (node.documentationComment == null) {
-        rule.reportLint(getNodeToAnnotate(node));
-      }
-    }
+    // // If no project info is set, bail early.
+    // // https://github.com/dart-lang/linter/issues/154
+    // var currentProject = rule.project;
+    // if (currentProject == null) {
+    //   return;
+    // }
+    //
+    // var declaredElement = node.declaredElement;
+    // if (declaredElement != null && currentProject.isApi(declaredElement)) {
+    //   if (node.documentationComment == null) {
+    //     rule.reportLint(getNodeToAnnotate(node));
+    //   }
+    // }
   }
 
   ///  classMember ::=
diff --git a/lib/src/rules/package_prefixed_library_names.dart b/lib/src/rules/package_prefixed_library_names.dart
index 95c7cb3..352c118 100644
--- a/lib/src/rules/package_prefixed_library_names.dart
+++ b/lib/src/rules/package_prefixed_library_names.dart
@@ -11,7 +11,6 @@
     r'Prefix library names with the package name and a dot-separated path.';
 
 const _details = r'''
-
 **DO** prefix library names with the package name and a dot-separated path.
 
 This guideline helps avoid the warnings you get when two libraries have the same
@@ -51,9 +50,12 @@
 bool matchesOrIsPrefixedBy(String name, String prefix) =>
     name == prefix || name.startsWith('$prefix.');
 
-class PackagePrefixedLibraryNames extends LintRule
-    implements ProjectVisitor, NodeLintRule {
-  DartProject? project;
+class PackagePrefixedLibraryNames extends LintRule {
+  static const LintCode code = LintCode(
+      'package_prefixed_library_names',
+      'The library name is not prefixed by the package name and a '
+          'dot-separated path.',
+      correctionMessage: "Try changing the name to '{0}'.");
 
   PackagePrefixedLibraryNames()
       : super(
@@ -63,7 +65,7 @@
             group: Group.style);
 
   @override
-  ProjectVisitor getProjectVisitor() => this;
+  LintCode get lintCode => code;
 
   @override
   void registerNodeProcessors(
@@ -71,11 +73,6 @@
     var visitor = _Visitor(this);
     registry.addLibraryDirective(this, visitor);
   }
-
-  @override
-  void visit(DartProject project) {
-    this.project = project;
-  }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
@@ -84,28 +81,33 @@
   _Visitor(this.rule);
 
   @override
+  // ignore: prefer_expression_function_bodies
   void visitLibraryDirective(LibraryDirective node) {
-    // If no project info is set, bail early.
-    // https://github.com/dart-lang/linter/issues/154
-    var project = rule.project;
-    var element = node.element;
-    if (project == null || element == null) {
-      return;
-    }
+    // Project info is not being set.
+    //See: https://github.com/dart-lang/linter/issues/3395
+    return;
 
-    var source = element.source;
-    if (source == null) {
-      return;
-    }
-
-    var prefix = Analyzer.facade.createLibraryNamePrefix(
-        libraryPath: source.fullName,
-        projectRoot: project.root.absolute.path,
-        packageName: project.name);
-
-    var name = element.name;
-    if (name == null || !matchesOrIsPrefixedBy(name, prefix)) {
-      rule.reportLint(node.name);
-    }
+    // // If no project info is set, bail early.
+    // // https://github.com/dart-lang/linter/issues/154
+    // var project = rule.project;
+    // var element = node.element;
+    // if (project == null || element == null) {
+    //   return;
+    // }
+    //
+    // var source = element.source;
+    // if (source == null) {
+    //   return;
+    // }
+    //
+    // var prefix = Analyzer.facade.createLibraryNamePrefix(
+    //     libraryPath: source.fullName,
+    //     projectRoot: project.root.absolute.path,
+    //     packageName: project.name);
+    //
+    // var name = element.name;
+    // if (name == null || !matchesOrIsPrefixedBy(name, prefix)) {
+    //   rule.reportLint(node.name, arguments: ['$prefix.$name']);
+    // }
   }
 }
diff --git a/lib/src/rules/parameter_assignments.dart b/lib/src/rules/parameter_assignments.dart
index 7739562..fdc8db6 100644
--- a/lib/src/rules/parameter_assignments.dart
+++ b/lib/src/rules/parameter_assignments.dart
@@ -3,17 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
 
 const _desc =
     r"Don't reassign references to parameters of functions or methods.";
 
 const _details = r'''
-
 **DON'T** assign new values to parameters of methods or functions.
 
 Assigning new values to parameters is generally a bad practice unless an
@@ -44,7 +41,7 @@
 **BAD:**
 ```dart
 class A {
-    void badMethod(int parameter) { // LINT
+  void badMethod(int parameter) { // LINT
     parameter = 4;
   }
 }
@@ -85,12 +82,6 @@
 bool _isDefaultFormalParameterWithDefaultValue(FormalParameter parameter) =>
     parameter is DefaultFormalParameter && parameter.defaultValue != null;
 
-bool _isDefaultFormalParameterWithoutDefaultValueReassigned(
-        FormalParameter parameter, AssignmentExpression assignment) =>
-    parameter is DefaultFormalParameter &&
-    parameter.defaultValue == null &&
-    _isFormalParameterReassigned(parameter, assignment);
-
 bool _isFormalParameterReassigned(
     FormalParameter parameter, AssignmentExpression assignment) {
   var leftHandSide = assignment.leftHandSide;
@@ -98,17 +89,7 @@
       leftHandSide.staticElement == parameter.declaredElement;
 }
 
-bool _preOrPostFixExpressionMutation(FormalParameter parameter, AstNode n) =>
-    n is PrefixExpression &&
-        n.operand is SimpleIdentifier &&
-        (n.operand as SimpleIdentifier).staticElement ==
-            parameter.declaredElement ||
-    n is PostfixExpression &&
-        n.operand is SimpleIdentifier &&
-        (n.operand as SimpleIdentifier).staticElement ==
-            parameter.declaredElement;
-
-class ParameterAssignments extends LintRule implements NodeLintRule {
+class ParameterAssignments extends LintRule {
   ParameterAssignments()
       : super(
             name: 'parameter_assignments',
@@ -125,6 +106,62 @@
   }
 }
 
+class _DeclarationVisitor extends RecursiveAstVisitor {
+  final FormalParameter parameter;
+  final LintRule rule;
+  final bool paramIsNotNullByDefault;
+  final bool paramDefaultsToNull;
+  bool hasBeenAssigned = false;
+
+  _DeclarationVisitor(this.parameter, this.rule,
+      {required this.paramIsNotNullByDefault,
+      required this.paramDefaultsToNull});
+
+  @override
+  visitAssignmentExpression(AssignmentExpression node) {
+    if (paramIsNotNullByDefault) {
+      if (_isFormalParameterReassigned(parameter, node)) {
+        rule.reportLint(node);
+      }
+    } else if (paramDefaultsToNull) {
+      if (_isFormalParameterReassigned(parameter, node)) {
+        if (hasBeenAssigned) {
+          rule.reportLint(node);
+        }
+        hasBeenAssigned = true;
+      }
+    }
+
+    super.visitAssignmentExpression(node);
+  }
+
+  @override
+  visitPostfixExpression(PostfixExpression node) {
+    if (paramIsNotNullByDefault) {
+      var operand = node.operand;
+      if (operand is SimpleIdentifier &&
+          operand.staticElement == parameter.declaredElement) {
+        rule.reportLint(node);
+      }
+    }
+
+    super.visitPostfixExpression(node);
+  }
+
+  @override
+  visitPrefixExpression(PrefixExpression node) {
+    if (paramIsNotNullByDefault) {
+      var operand = node.operand;
+      if (operand is SimpleIdentifier &&
+          operand.staticElement == parameter.declaredElement) {
+        rule.reportLint(node);
+      }
+    }
+
+    super.visitPrefixExpression(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
@@ -132,69 +169,32 @@
 
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
-    var parameters = node.functionExpression.parameters;
-    if (parameters != null) {
-      // Getter do not have formal parameters.
-      for (var e in parameters.parameters) {
-        var declaredElement = e.declaredElement;
-        if (declaredElement != null &&
-            node.functionExpression.body
-                .isPotentiallyMutatedInScope(declaredElement)) {
-          _reportIfSimpleParameterOrWithDefaultValue(e, node);
-        }
-      }
-    }
+    _checkParameters(
+        node.functionExpression.parameters, node.functionExpression.body);
   }
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    var parameterList = node.parameters;
-    if (parameterList != null) {
-      // Getters don't have parameters.
-      for (var e in parameterList.parameters) {
-        var declaredElement = e.declaredElement;
-        if (declaredElement != null &&
-            node.body.isPotentiallyMutatedInScope(declaredElement)) {
-          _reportIfSimpleParameterOrWithDefaultValue(e, node);
-        }
-      }
-    }
+    _checkParameters(node.parameters, node.body);
   }
 
-  void _reportIfSimpleParameterOrWithDefaultValue(
-      FormalParameter parameter, AstNode functionOrMethodDeclaration) {
-    var nodes =
-        DartTypeUtilities.traverseNodesInDFS(functionOrMethodDeclaration);
+  void _checkParameters(FormalParameterList? parameterList, FunctionBody body) {
+    if (parameterList == null) return;
 
-    if (parameter is SimpleFormalParameter ||
-        _isDefaultFormalParameterWithDefaultValue(parameter)) {
-      var mutatedNodes = nodes.where((n) =>
-          (n is AssignmentExpression &&
-              _isFormalParameterReassigned(parameter, n)) ||
-          _preOrPostFixExpressionMutation(parameter, n));
-      mutatedNodes.forEach(rule.reportLint);
-      return;
-    }
-
-    var assignmentsNodes = nodes
-        .where((n) =>
-            n is AssignmentExpression &&
-            _isDefaultFormalParameterWithoutDefaultValueReassigned(
-                parameter, n))
-        .toList();
-
-    var nonNullCoalescingAssignments = assignmentsNodes.where((n) =>
-        (n as AssignmentExpression).operator.type !=
-        TokenType.QUESTION_QUESTION_EQ);
-
-    if (assignmentsNodes.length > 1 ||
-        nonNullCoalescingAssignments.isNotEmpty) {
-      var node = assignmentsNodes.length > 1
-          ? assignmentsNodes.last
-          : nonNullCoalescingAssignments.isNotEmpty
-              ? nonNullCoalescingAssignments.first
-              : parameter;
-      rule.reportLint(node);
+    for (var parameter in parameterList.parameters) {
+      var declaredElement = parameter.declaredElement;
+      if (declaredElement != null &&
+          body.isPotentiallyMutatedInScope(declaredElement)) {
+        var paramIsNotNullByDefault = parameter is SimpleFormalParameter ||
+            _isDefaultFormalParameterWithDefaultValue(parameter);
+        var paramDefaultsToNull = parameter is DefaultFormalParameter &&
+            parameter.defaultValue == null;
+        if (paramDefaultsToNull || paramIsNotNullByDefault) {
+          body.accept(_DeclarationVisitor(parameter, rule,
+              paramDefaultsToNull: paramDefaultsToNull,
+              paramIsNotNullByDefault: paramIsNotNullByDefault));
+        }
+      }
     }
   }
 }
diff --git a/lib/src/rules/prefer_adjacent_string_concatenation.dart b/lib/src/rules/prefer_adjacent_string_concatenation.dart
index 6aa4eaf..a0efad7 100644
--- a/lib/src/rules/prefer_adjacent_string_concatenation.dart
+++ b/lib/src/rules/prefer_adjacent_string_concatenation.dart
@@ -10,7 +10,6 @@
 const _desc = r'Use adjacent strings to concatenate string literals.';
 
 const _details = r'''
-
 **DO** use adjacent strings to concatenate string literals.
 
 **BAD:**
@@ -29,8 +28,11 @@
 
 ''';
 
-class PreferAdjacentStringConcatenation extends LintRule
-    implements NodeLintRule {
+class PreferAdjacentStringConcatenation extends LintRule {
+  static const LintCode code = LintCode('prefer_adjacent_string_concatenation',
+      "String literals shouldn't be concatenated by the '+' operator.",
+      correctionMessage: 'Try removing the operator to use adjacent strings.');
+
   PreferAdjacentStringConcatenation()
       : super(
             name: 'prefer_adjacent_string_concatenation',
@@ -39,6 +41,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/prefer_asserts_in_initializer_lists.dart b/lib/src/rules/prefer_asserts_in_initializer_lists.dart
index 20267a9..eeaf513 100644
--- a/lib/src/rules/prefer_asserts_in_initializer_lists.dart
+++ b/lib/src/rules/prefer_asserts_in_initializer_lists.dart
@@ -9,31 +9,30 @@
 import '../analyzer.dart';
 import '../ast.dart';
 
-const _desc = r'Prefer putting asserts in initializer list.';
+const _desc = r'Prefer putting asserts in initializer lists.';
 
 const _details = r'''
-**DO** put asserts in initializer list for constructors with only asserts in
-their body.
-
-**GOOD:**
-```dart
-class A {
-  A(int a) : assert(a != null);
-}
-```
+**DO** put asserts in initializer lists.
 
 **BAD:**
 ```dart
 class A {
   A(int a) {
-    assert(a != null);
+    assert(a != 0);
   }
 }
 ```
 
+**GOOD:**
+```dart
+class A {
+  A(int a) : assert(a != 0);
+}
+```
+
 ''';
 
-class PreferAssertsInInitializerLists extends LintRule implements NodeLintRule {
+class PreferAssertsInInitializerLists extends LintRule {
   PreferAssertsInInitializerLists()
       : super(
             name: 'prefer_asserts_in_initializer_lists',
@@ -71,9 +70,7 @@
         element is PropertyAccessorElement &&
             !element.isStatic &&
             _hasAccessor(element) &&
-            !constructorElement.parameters
-                .whereType<FieldFormalParameterElement>()
-                .any((p) => p.field?.getter == element);
+            !_paramMatchesField(element, constructorElement.parameters);
   }
 
   @override
@@ -90,19 +87,36 @@
     var classes = classAndSuperClasses?.classes;
     return classes != null && classes.contains(element.enclosingElement);
   }
+
+  bool _paramMatchesField(
+      PropertyAccessorElement element, List<ParameterElement> parameters) {
+    for (var p in parameters) {
+      ParameterElement? parameterElement = p;
+      if (parameterElement is SuperFormalParameterElement) {
+        parameterElement = parameterElement.superConstructorParameter;
+      }
+
+      if (parameterElement is FieldFormalParameterElement) {
+        if (parameterElement.field?.getter == element) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
 }
 
 /// Lazy cache of elements.
 class _ClassAndSuperClasses {
   final ClassElement? element;
-  final Set<ClassElement> _classes = {};
+  final Set<InterfaceElement> _classes = {};
 
   _ClassAndSuperClasses(this.element);
 
   /// The [element] and its super classes, including mixins.
-  Set<ClassElement> get classes {
+  Set<InterfaceElement> get classes {
     if (_classes.isEmpty) {
-      void addRecursively(ClassElement? element) {
+      void addRecursively(InterfaceElement? element) {
         if (element != null && _classes.add(element)) {
           for (var t in element.mixins) {
             addRecursively(t.element);
diff --git a/lib/src/rules/prefer_asserts_with_message.dart b/lib/src/rules/prefer_asserts_with_message.dart
index 3bf8e67..163a0af 100644
--- a/lib/src/rules/prefer_asserts_with_message.dart
+++ b/lib/src/rules/prefer_asserts_with_message.dart
@@ -10,7 +10,6 @@
 const _desc = r'Prefer asserts with message.';
 
 const _details = r'''
-
 When assertions fail it's not always simple to understand why. Adding a message
 to the `assert` helps the developer to understand why the AssertionError occurs.
 
@@ -38,7 +37,7 @@
 
 ''';
 
-class PreferAssertsWithMessage extends LintRule implements NodeLintRule {
+class PreferAssertsWithMessage extends LintRule {
   PreferAssertsWithMessage()
       : super(
             name: 'prefer_asserts_with_message',
diff --git a/lib/src/rules/prefer_bool_in_asserts.dart b/lib/src/rules/prefer_bool_in_asserts.dart
index 56479c5..528eb04 100644
--- a/lib/src/rules/prefer_bool_in_asserts.dart
+++ b/lib/src/rules/prefer_bool_in_asserts.dart
@@ -11,7 +11,6 @@
 const _desc = r'Prefer using a boolean as the assert condition.';
 
 const _details = r'''
-
 **DO** use a boolean for assert conditions.
 
 Not using booleans in assert conditions can lead to code where it isn't clear
@@ -40,7 +39,7 @@
 The rule will be removed in a future Linter release.
 ''';
 
-class PreferBoolInAsserts extends LintRule implements NodeLintRule {
+class PreferBoolInAsserts extends LintRule {
   PreferBoolInAsserts()
       : super(
             name: 'prefer_bool_in_asserts',
diff --git a/lib/src/rules/prefer_collection_literals.dart b/lib/src/rules/prefer_collection_literals.dart
index 1a9141e..04a394d 100644
--- a/lib/src/rules/prefer_collection_literals.dart
+++ b/lib/src/rules/prefer_collection_literals.dart
@@ -7,30 +7,29 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Use collection literals when possible.';
 
 const _details = r'''
-
 **DO** use collection literals when possible.
 
 **BAD:**
 ```dart
 var points = List();
-var addresses = Map();
-var uniqueNames = Set();
-var ids = LinkedHashSet();
-var coordinates = LinkedHashMap();
+var addresses = Map<String, String>();
+var uniqueNames = Set<String>();
+var ids = LinkedHashSet<int>();
+var coordinates = LinkedHashMap<int, int>();
 ```
 
 **GOOD:**
 ```dart
 var points = [];
-var addresses = <String,String>{};
+var addresses = <String, String>{};
 var uniqueNames = <String>{};
 var ids = <int>{};
-var coordinates = <int,int>{};
+var coordinates = <int, int>{};
 ```
 
 **EXCEPTIONS:**
@@ -57,7 +56,11 @@
 ```
 ''';
 
-class PreferCollectionLiterals extends LintRule implements NodeLintRule {
+class PreferCollectionLiterals extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_collection_literals', 'Unnecessary constructor invocation.',
+      correctionMessage: 'Try using a collection literal.');
+
   PreferCollectionLiterals()
       : super(
             name: 'prefer_collection_literals',
@@ -66,6 +69,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -79,17 +85,6 @@
   _Visitor(this.rule);
 
   @override
-  void visitMethodInvocation(MethodInvocation node) {
-    // ['foo', 'bar', 'baz'].toSet();
-    if (node.methodName.name != 'toSet') {
-      return;
-    }
-    if (node.target is ListLiteral) {
-      rule.reportLint(node);
-    }
-  }
-
-  @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     var constructorName = node.constructorName.name?.name;
 
@@ -127,22 +122,37 @@
     }
   }
 
-  bool _isSet(Expression expression) => _isTypeSet(expression.staticType);
-  bool _isHashSet(Expression expression) =>
-      _isTypeHashSet(expression.staticType);
-  bool _isList(Expression expression) =>
-      DartTypeUtilities.isClass(expression.staticType, 'List', 'dart.core');
-  bool _isMap(Expression expression) => _isTypeMap(expression.staticType);
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    // ['foo', 'bar', 'baz'].toSet();
+    if (node.methodName.name != 'toSet') {
+      return;
+    }
+    if (node.target is ListLiteral) {
+      rule.reportLint(node);
+    }
+  }
+
   bool _isHashMap(Expression expression) =>
       _isTypeHashMap(expression.staticType);
-  bool _isTypeSet(DartType? type) =>
-      DartTypeUtilities.isClass(type, 'Set', 'dart.core');
-  bool _isTypeHashSet(DartType? type) =>
-      DartTypeUtilities.isClass(type, 'LinkedHashSet', 'dart.collection');
-  bool _isTypeMap(DartType? type) =>
-      DartTypeUtilities.isClass(type, 'Map', 'dart.core');
+
+  bool _isHashSet(Expression expression) =>
+      _isTypeHashSet(expression.staticType);
+
+  bool _isList(Expression expression) =>
+      expression.staticType?.isDartCoreList ?? false;
+
+  bool _isMap(Expression expression) =>
+      expression.staticType?.isDartCoreMap ?? false;
+
+  bool _isSet(Expression expression) =>
+      expression.staticType?.isDartCoreSet ?? false;
+
   bool _isTypeHashMap(DartType? type) =>
-      DartTypeUtilities.isClass(type, 'LinkedHashMap', 'dart.collection');
+      type.isSameAs('LinkedHashMap', 'dart.collection');
+
+  bool _isTypeHashSet(DartType? type) =>
+      type.isSameAs('LinkedHashSet', 'dart.collection');
 
   bool _shouldSkipLinkedHashLint(
       InstanceCreationExpression node, bool Function(DartType node) typeCheck) {
@@ -166,6 +176,15 @@
           return true;
         }
       }
+
+      // Skip: void f({required LinkedHashSet<Foo> s})
+      if (parent is NamedExpression) {
+        var paramType = parent.staticParameterElement?.type;
+        if (paramType != null && typeCheck(paramType)) {
+          return true;
+        }
+      }
+
       // Skip: <int, LinkedHashSet>{}.putIfAbsent(3, () => LinkedHashSet());
       // or <int, LinkedHashMap>{}.putIfAbsent(3, () => LinkedHashMap());
       if (parent is ExpressionFunctionBody) {
diff --git a/lib/src/rules/prefer_conditional_assignment.dart b/lib/src/rules/prefer_conditional_assignment.dart
index b22b365..5e3e285 100644
--- a/lib/src/rules/prefer_conditional_assignment.dart
+++ b/lib/src/rules/prefer_conditional_assignment.dart
@@ -7,12 +7,12 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
+import '../util/dart_type_utilities.dart' as type_utils;
 
 const _desc = r'Prefer using `??=` over testing for null.';
 
 const _details = r'''
-
 **PREFER** using `??=` over testing for null.
 
 As Dart has the `??=` operator, it is advisable to use it where applicable to
@@ -39,7 +39,7 @@
 
 bool _checkExpression(Expression expression, Expression condition) =>
     expression is AssignmentExpression &&
-    DartTypeUtilities.canonicalElementsFromIdentifiersAreEqual(
+    type_utils.canonicalElementsFromIdentifiersAreEqual(
         expression.leftHandSide, condition);
 
 bool _checkStatement(Statement statement, Expression condition) {
@@ -56,17 +56,21 @@
   var expression = rawExpression.unParenthesized;
   if (expression is BinaryExpression &&
       expression.operator.type == TokenType.EQ_EQ) {
-    if (DartTypeUtilities.isNullLiteral(expression.rightOperand)) {
+    if (expression.rightOperand.isNullLiteral) {
       return expression.leftOperand;
     }
-    if (DartTypeUtilities.isNullLiteral(expression.leftOperand)) {
+    if (expression.leftOperand.isNullLiteral) {
       return expression.rightOperand;
     }
   }
   return null;
 }
 
-class PreferConditionalAssignment extends LintRule implements NodeLintRule {
+class PreferConditionalAssignment extends LintRule {
+  static const LintCode code = LintCode('prefer_conditional_assignment',
+      "Unnecessary 'null' comparison before assigning a value.",
+      correctionMessage: "Try using the '??=' assignment operator.");
+
   PreferConditionalAssignment()
       : super(
             name: 'prefer_conditional_assignment',
@@ -75,6 +79,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/prefer_const_constructors.dart b/lib/src/rules/prefer_const_constructors.dart
index 8f7d861..9238634 100644
--- a/lib/src/rules/prefer_const_constructors.dart
+++ b/lib/src/rules/prefer_const_constructors.dart
@@ -4,17 +4,28 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../ast.dart';
 
 const _desc = r'Prefer const with constant constructors.';
 
 const _details = r'''
-
 **PREFER** using `const` for instantiating constant constructors.
 
-If a const constructor is available, it is preferable to use it.
+If a constructor can be invoked as const to produce a canonicalized instance,
+it's preferable to do so.
+
+**BAD:**
+```dart
+class A {
+  const A();
+}
+
+void accessA() {
+  A a = new A();
+}
+```
 
 **GOOD:**
 ```dart
@@ -38,20 +49,14 @@
 A foo(int x) => new A(x);
 ```
 
-**BAD:**
-```dart
-class A {
-  const A();
-}
-
-void accessA() {
-  A a = new A();
-}
-```
-
 ''';
 
-class PreferConstConstructors extends LintRule implements NodeLintRule {
+class PreferConstConstructors extends LintRule {
+  static const LintCode code = LintCode('prefer_const_constructors',
+      "Use 'const' with the constructor to improve performance.",
+      correctionMessage:
+          "Try adding the 'const' keyword to the constructor invocation.");
+
   PreferConstConstructors()
       : super(
             name: 'prefer_const_constructors',
@@ -60,6 +65,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -76,15 +84,18 @@
 
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (node.constructorName.type.isDeferred) return;
+
     var element = node.constructorName.staticElement;
+    if (element == null) return;
 
-    if (!node.isConst && element != null && element.isConst) {
+    if (element.isConst && !node.isConst) {
       // Handled by analyzer hint.
-      if (hasLiteralAnnotation(element)) {
-        return;
-      }
+      if (element.hasLiteral) return;
 
-      if (element.enclosingElement.isDartCoreObject) {
+      var enclosingElement = element.enclosingElement;
+      if (enclosingElement is ClassElement &&
+          enclosingElement.isDartCoreObject) {
         // Skip lint for `new Object()`, because it can be used for Id creation.
         return;
       }
diff --git a/lib/src/rules/prefer_const_constructors_in_immutables.dart b/lib/src/rules/prefer_const_constructors_in_immutables.dart
index 9cfcfd9..2de1781 100644
--- a/lib/src/rules/prefer_const_constructors_in_immutables.dart
+++ b/lib/src/rules/prefer_const_constructors_in_immutables.dart
@@ -12,21 +12,11 @@
 const _desc = r'Prefer declaring const constructors on `@immutable` classes.';
 
 const _details = r'''
-
 **PREFER** declaring const constructors on `@immutable` classes.
 
 If a class is immutable, it is usually a good idea to make its constructor a
 const constructor.
 
-**GOOD:**
-```dart
-@immutable
-class A {
-  final a;
-  const A(this.a);
-}
-```
-
 **BAD:**
 ```dart
 @immutable
@@ -36,6 +26,15 @@
 }
 ```
 
+**GOOD:**
+```dart
+@immutable
+class A {
+  final a;
+  const A(this.a);
+}
+```
+
 ''';
 
 /// The name of the top-level variable used to mark a immutable class.
@@ -49,8 +48,12 @@
     element.name == _immutableVarName &&
     element.library.name == _metaLibName;
 
-class PreferConstConstructorsInImmutables extends LintRule
-    implements NodeLintRule {
+class PreferConstConstructorsInImmutables extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_const_constructors_in_immutables',
+      "Constructors in '@immutable' classes should be declared as 'const'.",
+      correctionMessage: "Try adding 'const' to the constructor declaration.");
+
   PreferConstConstructorsInImmutables()
       : super(
             name: 'prefer_const_constructors_in_immutables',
@@ -59,6 +62,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -119,22 +125,22 @@
         supertype.constructors.firstWhere((e) => e.name.isEmpty).isConst;
   }
 
-  bool _hasImmutableAnnotation(ClassElement clazz) {
-    var selfAndInheritedClasses = _getSelfAndInheritedClasses(clazz);
-    var selfAndInheritedAnnotations =
-        selfAndInheritedClasses.expand((c) => c.metadata).map((m) => m.element);
-    return selfAndInheritedAnnotations.any(_isImmutable);
+  bool _hasImmutableAnnotation(InterfaceElement clazz) {
+    var selfAndInheritedClasses = _getSelfAndSuperClasses(clazz);
+    for (var cls in selfAndInheritedClasses) {
+      if (cls.metadata.any((m) => _isImmutable(m.element))) return true;
+    }
+    return false;
   }
 
-  bool _hasMixin(ClassElement clazz) => clazz.mixins.isNotEmpty;
+  bool _hasMixin(InterfaceElement clazz) => clazz.mixins.isNotEmpty;
 
-  static Iterable<ClassElement> _getSelfAndInheritedClasses(
-      ClassElement self) sync* {
-    ClassElement? current = self;
-    var seenElements = <ClassElement>{};
+  static List<InterfaceElement> _getSelfAndSuperClasses(InterfaceElement self) {
+    InterfaceElement? current = self;
+    var seenElements = <InterfaceElement>{};
     while (current != null && seenElements.add(current)) {
-      yield current;
       current = current.supertype?.element;
     }
+    return seenElements.toList();
   }
 }
diff --git a/lib/src/rules/prefer_const_declarations.dart b/lib/src/rules/prefer_const_declarations.dart
index 1012526..fa2fc34 100644
--- a/lib/src/rules/prefer_const_declarations.dart
+++ b/lib/src/rules/prefer_const_declarations.dart
@@ -12,21 +12,11 @@
 const _desc = r'Prefer const over final for declarations.';
 
 const _details = r'''
-
 **PREFER** using `const` for const declarations.
 
 Const declarations are more hot-reload friendly and allow to use const
 constructors if an instantiation references this declaration.
 
-**GOOD:**
-```dart
-const o = <int>[];
-
-class A {
-  static const o = <int>[];
-}
-```
-
 **BAD:**
 ```dart
 final o = const <int>[];
@@ -36,9 +26,22 @@
 }
 ```
 
+**GOOD:**
+```dart
+const o = <int>[];
+
+class A {
+  static const o = <int>[];
+}
+```
+
 ''';
 
-class PreferConstDeclarations extends LintRule implements NodeLintRule {
+class PreferConstDeclarations extends LintRule {
+  static const LintCode code = LintCode('prefer_const_declarations',
+      "Use 'const' for final variables initialized to a constant value.",
+      correctionMessage: "Try replacing 'final' with 'const'.");
+
   PreferConstDeclarations()
       : super(
             name: 'prefer_const_declarations',
@@ -47,6 +50,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
diff --git a/lib/src/rules/prefer_const_literals_to_create_immutables.dart b/lib/src/rules/prefer_const_literals_to_create_immutables.dart
index cd76706..cf0a31d 100644
--- a/lib/src/rules/prefer_const_literals_to_create_immutables.dart
+++ b/lib/src/rules/prefer_const_literals_to_create_immutables.dart
@@ -13,7 +13,6 @@
     'Prefer const literals as parameters of constructors on @immutable classes.';
 
 const details = '''
-
 **PREFER** using `const` for instantiating list, map and set literals used as
 parameters in immutable class instantiations.
 
@@ -48,8 +47,13 @@
     element.name == _immutableVarName &&
     element.library.name == _metaLibName;
 
-class PreferConstLiteralsToCreateImmutables extends LintRule
-    implements NodeLintRule {
+class PreferConstLiteralsToCreateImmutables extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_const_literals_to_create_immutables',
+      "Use 'const' literals as arguments to constructors of '@immutable' "
+          'classes.',
+      correctionMessage: "Try adding 'const' before the literal.");
+
   PreferConstLiteralsToCreateImmutables()
       : super(
             name: 'prefer_const_literals_to_create_immutables',
@@ -58,6 +62,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -81,30 +88,6 @@
     _visitTypedLiteral(node);
   }
 
-  Iterable<InterfaceType> _getSelfAndInheritedTypes(InterfaceType type) sync* {
-    InterfaceType? current = type;
-    // TODO(a14n) the is check looks unnecessary but prevents https://github.com/dart-lang/sdk/issues/33210
-    // for now it's not clear how this can happen
-    while (current != null && current is InterfaceType) {
-      yield current;
-      current = current.superclass;
-    }
-  }
-
-  bool _hasImmutableAnnotation(DartType? type) {
-    if (type is! InterfaceType) {
-      // This happens when we find an instance creation expression for a class
-      // that cannot be resolved.
-      return false;
-    }
-    var inheritedAndSelfTypes = _getSelfAndInheritedTypes(type);
-    var inheritedAndSelfAnnotations = inheritedAndSelfTypes
-        .map((type) => type.element)
-        .expand((c) => c.metadata)
-        .map((m) => m.element);
-    return inheritedAndSelfAnnotations.any(_isImmutable);
-  }
-
   void _visitTypedLiteral(TypedLiteral literal) {
     if (literal.isConst) return;
 
@@ -128,4 +111,23 @@
       rule.reportLint(literal);
     }
   }
+
+  // todo(pq): consider making this a utility and sharing w/ `avoid_equals_and_hash_code_on_mutable_classes`
+  static bool _hasImmutableAnnotation(DartType? type) {
+    if (type is! InterfaceType) {
+      // This happens when we find an instance creation expression for a class
+      // that cannot be resolved.
+      return false;
+    }
+
+    InterfaceType? current = type;
+    while (current != null) {
+      for (var annotation in current.element.metadata) {
+        if (_isImmutable(annotation.element)) return true;
+      }
+      current = current.superclass;
+    }
+
+    return false;
+  }
 }
diff --git a/lib/src/rules/prefer_constructors_over_static_methods.dart b/lib/src/rules/prefer_constructors_over_static_methods.dart
index 33991a4..2527a0d 100644
--- a/lib/src/rules/prefer_constructors_over_static_methods.dart
+++ b/lib/src/rules/prefer_constructors_over_static_methods.dart
@@ -7,13 +7,11 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
 
 const _desc =
     r'Prefer defining constructors instead of static methods to create instances.';
 
 const _details = r'''
-
 **PREFER** defining constructors instead of static methods to create instances.
 
 In most cases, it makes more sense to use a named constructor rather than a
@@ -43,16 +41,10 @@
 ```
 ''';
 
-bool _hasNewInvocation(DartType returnType, FunctionBody body) {
-  bool _isInstanceCreationExpression(AstNode node) =>
-      node is InstanceCreationExpression && node.staticType == returnType;
+bool _hasNewInvocation(DartType returnType, FunctionBody body) =>
+    _BodyVisitor(returnType).containsInstanceCreation(body);
 
-  return DartTypeUtilities.traverseNodesInDFS(body)
-      .any(_isInstanceCreationExpression);
-}
-
-class PreferConstructorsInsteadOfStaticMethods extends LintRule
-    implements NodeLintRule {
+class PreferConstructorsInsteadOfStaticMethods extends LintRule {
   PreferConstructorsInsteadOfStaticMethods()
       : super(
             name: 'prefer_constructors_over_static_methods',
@@ -68,6 +60,26 @@
   }
 }
 
+class _BodyVisitor extends RecursiveAstVisitor {
+  bool found = false;
+
+  final DartType returnType;
+  _BodyVisitor(this.returnType);
+
+  bool containsInstanceCreation(FunctionBody body) {
+    body.accept(this);
+    return found;
+  }
+
+  @override
+  visitInstanceCreationExpression(InstanceCreationExpression node) {
+    found = node.staticType == returnType;
+    if (!found) {
+      super.visitInstanceCreationExpression(node);
+    }
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
   final LinterContext context;
@@ -79,7 +91,7 @@
     var returnType = node.returnType?.type;
     var parent = node.parent;
     if (node.isStatic &&
-        parent is ClassOrMixinDeclaration &&
+        parent is ClassDeclaration &&
         returnType is InterfaceType &&
         parent.typeParameters == null &&
         node.typeParameters == null) {
@@ -90,7 +102,7 @@
           return;
         }
         if (_hasNewInvocation(returnType, node.body)) {
-          rule.reportLint(node.name);
+          rule.reportLintForToken(node.name);
         }
       }
     }
diff --git a/lib/src/rules/prefer_contains.dart b/lib/src/rules/prefer_contains.dart
index 010ec3f..d4f214c 100644
--- a/lib/src/rules/prefer_contains.dart
+++ b/lib/src/rules/prefer_contains.dart
@@ -7,18 +7,12 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
+import '../ast.dart';
+import '../extensions.dart';
 import '../util/dart_type_utilities.dart';
 
-const alwaysFalse =
-    'Always false because indexOf is always greater or equal -1.';
-
-const alwaysTrue = 'Always true because indexOf is always greater or equal -1.';
-
-const useContains = 'Use contains instead of indexOf';
 const _desc = r'Use contains for `List` and `String` instances.';
-
 const _details = r'''
-
 **DON'T** use `indexOf` to see if a collection contains an element.
 
 Calling `indexOf` to see if a collection contains something is difficult to read
@@ -26,19 +20,32 @@
 
 Instead, prefer `contains`.
 
+**BAD:**
+```dart
+if (lunchBox.indexOf('sandwich') == -1) return 'so hungry...';
+```
+
 **GOOD:**
 ```dart
 if (!lunchBox.contains('sandwich')) return 'so hungry...';
 ```
 
-**BAD:**
-```dart
-if (lunchBox.indexOf('sandwich')) == -1 return 'so hungry...';
-```
-
 ''';
 
-class PreferContainsOverIndexOf extends LintRule implements NodeLintRule {
+class PreferContainsOverIndexOf extends LintRule {
+  // TODO(brianwilkerson) Both `alwaysFalse` and `alwaysTrue` should be warnings
+  //  rather than lints because they represent a bug rather than a style
+  //  preference.
+  static const LintCode alwaysFalse = LintCode('prefer_contains',
+      'Always false because indexOf is always greater or equal -1.');
+
+  static const LintCode alwaysTrue = LintCode('prefer_contains',
+      'Always true because indexOf is always greater or equal -1.');
+
+  static const LintCode useContains = LintCode('prefer_contains',
+      "Unnecessary use of 'indexOf' to test for containment.",
+      correctionMessage: "Try using 'contains'.");
+
   PreferContainsOverIndexOf()
       : super(
             name: 'prefer_contains',
@@ -47,27 +54,14 @@
             group: Group.style);
 
   @override
+  List<LintCode> get lintCodes => [alwaysFalse, alwaysTrue, useContains];
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
-    registry.addMethodInvocation(this, visitor);
+    registry.addBinaryExpression(this, visitor);
   }
-
-  void reportLintWithDescription(AstNode? node, String description) {
-    if (node != null) {
-      reporter.reportErrorForNode(_LintCode(name, description), node, []);
-    }
-  }
-}
-
-/// TODO create common MultiMessageLintCode class
-class _LintCode extends LintCode {
-  static final registry = <String, _LintCode>{};
-
-  factory _LintCode(String name, String message) =>
-      registry.putIfAbsent(name + message, () => _LintCode._(name, message));
-
-  _LintCode._(String name, String message) : super(name, message);
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
@@ -78,93 +72,70 @@
   _Visitor(this.rule, this.context);
 
   @override
-  void visitMethodInvocation(MethodInvocation node) {
-    if (node.methodName.name != 'indexOf') {
+  void visitBinaryExpression(BinaryExpression node) {
+    // This lint rule is only concerned with these operators.
+    if (!node.operator.matchesAny([
+      TokenType.EQ_EQ,
+      TokenType.BANG_EQ,
+      TokenType.GT,
+      TokenType.GT_EQ,
+      TokenType.LT,
+      TokenType.LT_EQ,
+    ])) {
       return;
     }
-
-    var parentType = node.target?.staticType;
-    if (parentType == null ||
-        !DartTypeUtilities.implementsAnyInterface(parentType, [
-          InterfaceTypeDefinition('Iterable', 'dart.core'),
-          InterfaceTypeDefinition('String', 'dart.core'),
-        ])) {
-      return;
-    }
-
-    if (node.parent is AssignmentExpression) {
-      // The result of `indexOf` is being assigned before being compared, so
-      // it's important. E.g.  `(next = list.indexOf('{')) != -1)`.
-      return;
-    }
-
-    // Going up in AST structure to find binary comparison operator for this
-    // `indexOf` access. Most of the time it will be a parent, but sometimes
-    // it can be wrapped in parentheses or `as` operator.
-    AstNode? search = node;
-    while (
-        search != null && search is Expression && search is! BinaryExpression) {
-      search = search.parent;
-    }
-
-    if (search is! BinaryExpression) {
-      return;
-    }
-
-    var binaryExpression = search;
-    var operator = binaryExpression.operator;
-
-    // Comparing constants with result of indexOf.
-
-    var rightOperand = binaryExpression.rightOperand;
-    var rightValue = context.evaluateConstant(rightOperand).value;
-
-    if (rightValue != null && (rightValue.type?.isDartCoreInt ?? false)) {
-      // Constant is on right side of comparison operator
-      _checkConstant(binaryExpression, rightValue.toIntValue(), operator.type);
-      return;
-    }
-
-    var leftOperand = binaryExpression.leftOperand;
-    var leftValue = context.evaluateConstant(leftOperand).value;
-    if (leftValue != null && (leftValue.type?.isDartCoreInt ?? false)) {
-      // Constants is on left side of comparison operator
-      _checkConstant(binaryExpression, leftValue.toIntValue(),
-          _invertedTokenType(operator.type));
+    var value = getIntValue(node.rightOperand, context);
+    if (value is int) {
+      if (value <= 0 && _isUnassignedIndexOf(node.leftOperand)) {
+        _checkConstant(node, value, node.operator.type);
+      }
+    } else {
+      value = getIntValue(node.leftOperand, context);
+      if (value is int) {
+        if (value <= 0 && _isUnassignedIndexOf(node.rightOperand)) {
+          _checkConstant(node, value, _invertedTokenType(node.operator.type));
+        }
+      }
     }
   }
 
-  void _checkConstant(Expression expression, int? value, TokenType type) {
+  void _checkConstant(Expression expression, int value, TokenType type) {
     if (value == -1) {
       if (type == TokenType.EQ_EQ ||
           type == TokenType.BANG_EQ ||
           type == TokenType.LT_EQ ||
           type == TokenType.GT) {
-        rule.reportLintWithDescription(expression, useContains);
+        rule.reportLint(expression,
+            errorCode: PreferContainsOverIndexOf.useContains);
       } else if (type == TokenType.LT) {
         // indexOf < -1 is always false
-        rule.reportLintWithDescription(expression, alwaysFalse);
+        rule.reportLint(expression,
+            errorCode: PreferContainsOverIndexOf.alwaysFalse);
       } else if (type == TokenType.GT_EQ) {
         // indexOf >= -1 is always true
-        rule.reportLintWithDescription(expression, alwaysTrue);
+        rule.reportLint(expression,
+            errorCode: PreferContainsOverIndexOf.alwaysTrue);
       }
     } else if (value == 0) {
       // 'indexOf >= 0' is same as 'contains',
       // and 'indexOf < 0' is same as '!contains'
       if (type == TokenType.GT_EQ || type == TokenType.LT) {
-        rule.reportLintWithDescription(expression, useContains);
+        rule.reportLint(expression,
+            errorCode: PreferContainsOverIndexOf.useContains);
       }
-    } else if (value! < -1) {
+    } else if (value < -1) {
       // 'indexOf' is always >= -1, so comparing with lesser values makes
       // no sense.
       if (type == TokenType.EQ_EQ ||
           type == TokenType.LT_EQ ||
           type == TokenType.LT) {
-        rule.reportLintWithDescription(expression, alwaysFalse);
+        rule.reportLint(expression,
+            errorCode: PreferContainsOverIndexOf.alwaysFalse);
       } else if (type == TokenType.BANG_EQ ||
           type == TokenType.GT_EQ ||
           type == TokenType.GT) {
-        rule.reportLintWithDescription(expression, alwaysTrue);
+        rule.reportLint(expression,
+            errorCode: PreferContainsOverIndexOf.alwaysTrue);
       }
     }
   }
@@ -187,4 +158,39 @@
         return type;
     }
   }
+
+  /// Returns whether [expression] is an invocation of `Iterable.indexOf` or
+  /// `String.indexOf`, which is not assigned to a value.
+  bool _isUnassignedIndexOf(Expression expression) {
+    // Unwrap parens and `as` expressions.
+    var invocation = expression.unParenthesized;
+    while (invocation is AsExpression) {
+      invocation = invocation.expression;
+    }
+    invocation = invocation.unParenthesized;
+
+    if (invocation is! MethodInvocation) return false;
+
+    // The result of `indexOf` is being assigned before being compared, so
+    // it's important. E.g.  `(next = list.indexOf('{')) != -1)`.
+    if (invocation.parent is AssignmentExpression) return false;
+    if (invocation.methodName.name != 'indexOf') return false;
+
+    var parentType = invocation.target?.staticType;
+    if (parentType == null) return false;
+    if (!parentType.implementsAnyInterface([
+      InterfaceTypeDefinition('Iterable', 'dart.core'),
+      InterfaceTypeDefinition('String', 'dart.core'),
+    ])) {
+      return false;
+    }
+
+    var args = invocation.argumentList.arguments;
+    if (args.length == 2) {
+      var start = args[1];
+      if (getIntValue(start, context) != 0) return false;
+    }
+
+    return true;
+  }
 }
diff --git a/lib/src/rules/prefer_double_quotes.dart b/lib/src/rules/prefer_double_quotes.dart
index d553990..89e6b25 100644
--- a/lib/src/rules/prefer_double_quotes.dart
+++ b/lib/src/rules/prefer_double_quotes.dart
@@ -9,7 +9,6 @@
     r"Prefer double quotes where they won't require escape sequences.";
 
 const _details = '''
-
 **DO** use double quotes where they wouldn't require additional escapes.
 
 That means strings with a double quote may use apostrophes so that the double
@@ -42,7 +41,7 @@
 
 ''';
 
-class PreferDoubleQuotes extends LintRule implements NodeLintRule {
+class PreferDoubleQuotes extends LintRule {
   PreferDoubleQuotes()
       : super(
             name: 'prefer_double_quotes',
diff --git a/lib/src/rules/prefer_equal_for_default_values.dart b/lib/src/rules/prefer_equal_for_default_values.dart
index adeca12..a705e3f 100644
--- a/lib/src/rules/prefer_equal_for_default_values.dart
+++ b/lib/src/rules/prefer_equal_for_default_values.dart
@@ -2,19 +2,20 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/dart/ast/token.dart';
-import 'package:analyzer/dart/ast/visitor.dart';
-
 import '../analyzer.dart';
 
 const _desc = r'Use `=` to separate a named parameter from its default value.';
 
 const _details = r'''
+**DEPRECATED:** In Dart 2.19, 
+the Dart analyzer reports the old `:` syntax as a warning
+and will report it as an error in Dart 3.0.
+As a result, this rule is unmaintained 
+and will be removed in a future Linter release.
 
 From the [style guide](https://dart.dev/guides/language/effective-dart/usage):
 
-**DO** Use `=` to separate a named parameter from its default value.
+**DO** use `=` to separate a named parameter from its default value.
 
 **BAD:**
 ```dart
@@ -25,34 +26,24 @@
 ```dart
 m({a = 1})
 ```
-
 ''';
 
-class PreferEqualForDefaultValues extends LintRule implements NodeLintRule {
+class PreferEqualForDefaultValues extends LintRule {
+  static const LintCode code = LintCode('prefer_equal_for_default_values',
+      "Default values should be introduced by '=' rather than ':'.",
+      correctionMessage: "Try using '=' to introduce the default value.");
+
   PreferEqualForDefaultValues()
       : super(
             name: 'prefer_equal_for_default_values',
             description: _desc,
             details: _details,
+            maturity: Maturity.deprecated,
             group: Group.style);
 
   @override
-  void registerNodeProcessors(
-      NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this);
-    registry.addDefaultFormalParameter(this, visitor);
-  }
-}
+  LintCode get lintCode => code;
 
-class _Visitor extends SimpleAstVisitor<void> {
-  final LintRule rule;
-
-  _Visitor(this.rule);
-
-  @override
-  void visitDefaultFormalParameter(DefaultFormalParameter node) {
-    if (node.isNamed && node.separator?.type == TokenType.COLON) {
-      rule.reportLintForToken(node.separator);
-    }
-  }
+  // As of 2.19, this is a warning so we don't want to double-report and so
+  // we don't register any processors.
 }
diff --git a/lib/src/rules/prefer_expression_function_bodies.dart b/lib/src/rules/prefer_expression_function_bodies.dart
index 0c79608..4a3734c 100644
--- a/lib/src/rules/prefer_expression_function_bodies.dart
+++ b/lib/src/rules/prefer_expression_function_bodies.dart
@@ -11,7 +11,6 @@
     r'Use => for short members whose body is a single return statement.';
 
 const _details = r'''
-
 **CONSIDER** using => for short members whose body is a single return statement.
 
 **BAD:**
@@ -52,7 +51,7 @@
 
 ''';
 
-class PreferExpressionFunctionBodies extends LintRule implements NodeLintRule {
+class PreferExpressionFunctionBodies extends LintRule {
   PreferExpressionFunctionBodies()
       : super(
             name: 'prefer_expression_function_bodies',
diff --git a/lib/src/rules/prefer_final_fields.dart b/lib/src/rules/prefer_final_fields.dart
index 932ebb0..2657337 100644
--- a/lib/src/rules/prefer_final_fields.dart
+++ b/lib/src/rules/prefer_final_fields.dart
@@ -10,12 +10,11 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Private field could be final.';
 
 const _details = r'''
-
 **DO** prefer declaring private fields as final if they are not reassigned later
 in the library.
 
@@ -85,7 +84,7 @@
 ''';
 
 bool _containedInFormal(Element element, FormalParameter formal) {
-  var formalField = formal.identifier?.staticElement;
+  var formalField = formal.declaredElement;
   return formalField is FieldFormalParameterElement &&
       formalField.field == element;
 }
@@ -93,11 +92,13 @@
 bool _containedInInitializer(
         Element element, ConstructorInitializer initializer) =>
     initializer is ConstructorFieldInitializer &&
-    DartTypeUtilities.getCanonicalElementFromIdentifier(
-            initializer.fieldName) ==
-        element;
+    initializer.fieldName.canonicalElement == element;
 
-class PreferFinalFields extends LintRule implements NodeLintRule {
+class PreferFinalFields extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_final_fields', "The private field {0} could be 'final'.",
+      correctionMessage: "Try making the field 'final'.");
+
   PreferFinalFields()
       : super(
             name: 'prefer_final_fields',
@@ -106,6 +107,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -142,8 +146,7 @@
   }
 
   void _addMutatedFieldElement(CompoundAssignmentExpression assignment) {
-    var element =
-        DartTypeUtilities.getCanonicalElement(assignment.writeElement);
+    var element = assignment.writeElement?.canonicalElement;
     if (element is FieldElement) {
       _mutatedFields.add(element);
     }
@@ -164,6 +167,8 @@
 
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
+    if (node.parent is EnumDeclaration) return;
+
     var fields = node.fields;
     if (fields.isFinal || fields.isConst) {
       return;
@@ -190,10 +195,10 @@
 
         if (isFieldInConstructors) {
           if (isFieldInAllConstructors) {
-            rule.reportLint(variable);
+            rule.reportLint(variable, arguments: [variable.name.lexeme]);
           }
         } else if (element.hasInitializer) {
-          rule.reportLint(variable);
+          rule.reportLint(variable, arguments: [variable.name.lexeme]);
         }
       }
     }
diff --git a/lib/src/rules/prefer_final_in_for_each.dart b/lib/src/rules/prefer_final_in_for_each.dart
index 29c954b..67fe263 100644
--- a/lib/src/rules/prefer_final_in_for_each.dart
+++ b/lib/src/rules/prefer_final_in_for_each.dart
@@ -11,7 +11,6 @@
     r'Prefer final in for-each loop variable if reference is not reassigned.';
 
 const _details = r'''
-
 **DO** prefer declaring for-each loop variables as final if they are not
 reassigned later in the code.
 
@@ -43,7 +42,7 @@
 
 ''';
 
-class PreferFinalInForEach extends LintRule implements NodeLintRule {
+class PreferFinalInForEach extends LintRule {
   PreferFinalInForEach()
       : super(
             name: 'prefer_final_in_for_each',
@@ -83,7 +82,7 @@
       if (function != null &&
           loopVariableElement != null &&
           !function.isPotentiallyMutatedInScope(loopVariableElement)) {
-        rule.reportLint(loopVariable.identifier);
+        rule.reportLintForToken(loopVariable.name);
       }
     }
   }
diff --git a/lib/src/rules/prefer_final_locals.dart b/lib/src/rules/prefer_final_locals.dart
index f5f5f6d..eab7707 100644
--- a/lib/src/rules/prefer_final_locals.dart
+++ b/lib/src/rules/prefer_final_locals.dart
@@ -11,7 +11,6 @@
     r'Prefer final for variable declarations if they are not reassigned.';
 
 const _details = r'''
-
 **DO** prefer declaring variables as final if they are not reassigned later in
 the code.
 
@@ -46,7 +45,7 @@
 
 ''';
 
-class PreferFinalLocals extends LintRule implements NodeLintRule {
+class PreferFinalLocals extends LintRule {
   PreferFinalLocals()
       : super(
             name: 'prefer_final_locals',
@@ -61,7 +60,7 @@
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
-    registry.addVariableDeclaration(this, visitor);
+    registry.addVariableDeclarationList(this, visitor);
   }
 }
 
@@ -71,20 +70,26 @@
   _Visitor(this.rule);
 
   @override
-  void visitVariableDeclaration(VariableDeclaration node) {
-    if (node.isConst ||
-        node.isFinal ||
-        node.equals == null ||
-        node.initializer == null) {
-      return;
-    }
+  void visitVariableDeclarationList(VariableDeclarationList node) {
+    if (node.isConst || node.isFinal) return;
 
     var function = node.thisOrAncestorOfType<FunctionBody>();
-    var declaredElement = node.declaredElement;
-    if (function != null &&
-        declaredElement != null &&
-        !function.isPotentiallyMutatedInScope(declaredElement)) {
-      rule.reportLint(node.name);
+    if (function == null) return;
+
+    for (var variable in node.variables) {
+      if (variable.equals == null || variable.initializer == null) {
+        return;
+      }
+      var declaredElement = variable.declaredElement;
+      if (declaredElement != null &&
+          function.isPotentiallyMutatedInScope(declaredElement)) {
+        return;
+      }
+    }
+    if (node.keyword != null) {
+      rule.reportLintForToken(node.keyword);
+    } else if (node.type != null) {
+      rule.reportLint(node.type);
     }
   }
 }
diff --git a/lib/src/rules/prefer_final_parameters.dart b/lib/src/rules/prefer_final_parameters.dart
index 3cc3c74..ff04806 100644
--- a/lib/src/rules/prefer_final_parameters.dart
+++ b/lib/src/rules/prefer_final_parameters.dart
@@ -11,7 +11,6 @@
     r'Prefer final for parameter declarations if they are not reassigned.';
 
 const _details = r'''
-
 **DO** prefer declaring parameters as final if they are not reassigned in
 the function body.
 
@@ -63,7 +62,7 @@
 
 ''';
 
-class PreferFinalParameters extends LintRule implements NodeLintRule {
+class PreferFinalParameters extends LintRule {
   PreferFinalParameters()
       : super(
             name: 'prefer_final_parameters',
@@ -72,7 +71,8 @@
             group: Group.style);
 
   @override
-  List<String> get incompatibleRules => const ['unnecessary_final'];
+  List<String> get incompatibleRules =>
+      const ['unnecessary_final', 'avoid_final_parameters'];
 
   @override
   void registerNodeProcessors(
@@ -95,11 +95,22 @@
       FormalParameterList? parameters, FunctionBody body) {
     if (parameters != null) {
       for (var param in parameters.parameters) {
-        if (param.isFinal || param.isConst || param is FieldFormalParameter) {
+        if (param is DefaultFormalParameter) {
+          param = param.parameter;
+        }
+        if (param.isFinal ||
+            param.isConst ||
+            // A field formal parameter is final even without the `final`
+            // modifier.
+            param is FieldFormalParameter ||
+            // A super formal parameter is final even without the `final`
+            // modifier.
+            param is SuperFormalParameter) {
           continue;
         }
         var declaredElement = param.declaredElement;
         if (declaredElement != null &&
+            !declaredElement.isInitializingFormal &&
             !body.isPotentiallyMutatedInScope(declaredElement)) {
           rule.reportLint(param);
         }
diff --git a/lib/src/rules/prefer_for_elements_to_map_fromIterable.dart b/lib/src/rules/prefer_for_elements_to_map_fromIterable.dart
index 4fcd29a..5d02ba4 100644
--- a/lib/src/rules/prefer_for_elements_to_map_fromIterable.dart
+++ b/lib/src/rules/prefer_for_elements_to_map_fromIterable.dart
@@ -8,10 +8,19 @@
 
 import '../analyzer.dart';
 
-const _desc = r'Prefer for elements when building maps from iterables.';
+const _desc = r"Prefer 'for' elements when building maps from iterables.";
 
 const _details = r'''
-When building maps from iterables, it is preferable to use for elements.
+When building maps from iterables, it is preferable to use 'for' elements.
+
+Using 'for' elements brings several benefits including:
+
+- Performance
+- Flexibility
+- Readability
+- Improved type inference
+- Improved interaction with null safety
+
 
 **BAD:**
 ```dart
@@ -30,10 +39,25 @@
     '${demo.routeName}': demo.buildRoute,
 };
 ```
+
+**GOOD:**
+```dart
+// Map<int, Student> is not required, type is inferred automatically.
+final pizzaRecipients = {
+  ...studentLeaders,
+  for (var student in classG)
+    if (student.isPassing) student.id: student,
+};
+```
 ''';
 
-class PreferForElementsToMapFromIterable extends LintRule
-    implements NodeLintRule {
+class PreferForElementsToMapFromIterable extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_for_elements_to_map_fromIterable',
+      "Use 'for' elements when building maps from iterables.",
+      correctionMessage:
+          "Try using a collection literal with a 'for' element.");
+
   PreferForElementsToMapFromIterable()
       : super(
             name: 'prefer_for_elements_to_map_fromIterable',
@@ -42,6 +66,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -82,7 +109,7 @@
       } else if (body is BlockFunctionBody) {
         var statements = body.block.statements;
         if (statements.length == 1) {
-          var statement = statements[0];
+          var statement = statements.first;
           if (statement is ReturnStatement) {
             return statement.expression;
           }
@@ -98,7 +125,7 @@
           var parameters = expression.parameters?.parameters;
           if (parameters != null &&
               parameters.length == 1 &&
-              parameters[0].isRequired) {
+              parameters.first.isRequired) {
             if (extractBody(expression) != null) {
               return expression;
             }
diff --git a/lib/src/rules/prefer_foreach.dart b/lib/src/rules/prefer_foreach.dart
index c4f7f80..b4a3faa 100644
--- a/lib/src/rules/prefer_foreach.dart
+++ b/lib/src/rules/prefer_foreach.dart
@@ -7,12 +7,11 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Use `forEach` to only apply a function to all the elements.';
 
 const _details = r'''
-
 **DO** use `forEach` if you are only going to apply a function or a method
 to all the elements of an iterable.
 
@@ -44,7 +43,7 @@
 
 ''';
 
-class PreferForeach extends LintRule implements NodeLintRule {
+class PreferForeach extends LintRule {
   PreferForeach()
       : super(
             name: 'prefer_foreach',
@@ -95,9 +94,7 @@
   @override
   void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
     var arguments = node.argumentList.arguments;
-    if (arguments.length == 1 &&
-        DartTypeUtilities.getCanonicalElementFromIdentifier(arguments.first) ==
-            element) {
+    if (arguments.length == 1 && arguments.first.canonicalElement == element) {
       rule.reportLint(forEachStatement);
     }
   }
@@ -107,14 +104,8 @@
     var arguments = node.argumentList.arguments;
     var target = node.target;
     if (arguments.length == 1 &&
-        DartTypeUtilities.getCanonicalElementFromIdentifier(arguments.first) ==
-            element &&
-        (target == null ||
-            (DartTypeUtilities.getCanonicalElementFromIdentifier(target) !=
-                    element &&
-                !DartTypeUtilities.traverseNodesInDFS(target)
-                    .map(DartTypeUtilities.getCanonicalElementFromIdentifier)
-                    .contains(element)))) {
+        arguments.first.canonicalElement == element &&
+        (target == null || !_ReferenceFinder(element).references(target))) {
       rule.reportLint(forEachStatement);
     }
   }
@@ -125,6 +116,29 @@
   }
 }
 
+class _ReferenceFinder extends UnifyingAstVisitor {
+  bool found = false;
+  final LocalVariableElement? element;
+  _ReferenceFinder(this.element);
+
+  bool references(Expression target) {
+    if (target.canonicalElement == element) return true;
+
+    target.accept(this);
+    return found;
+  }
+
+  @override
+  visitNode(AstNode node) {
+    if (found) return;
+
+    found = node.canonicalElement == element;
+    if (!found) {
+      super.visitNode(node);
+    }
+  }
+}
+
 class _Visitor extends SimpleAstVisitor {
   final LintRule rule;
   _Visitor(this.rule);
diff --git a/lib/src/rules/prefer_function_declarations_over_variables.dart b/lib/src/rules/prefer_function_declarations_over_variables.dart
index bf1fce3..b8c01dd 100644
--- a/lib/src/rules/prefer_function_declarations_over_variables.dart
+++ b/lib/src/rules/prefer_function_declarations_over_variables.dart
@@ -10,7 +10,6 @@
 const _desc = r'Use a function declaration to bind a function to a name.';
 
 const _details = r'''
-
 **DO** use a function declaration to bind a function to a name.
 
 As Dart allows local function declarations, it is a good practice to use them in
@@ -36,8 +35,14 @@
 
 ''';
 
-class PreferFunctionDeclarationsOverVariables extends LintRule
-    implements NodeLintRule {
+class PreferFunctionDeclarationsOverVariables extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_function_declarations_over_variables',
+      'Use a function declaration rather than a variable assignment to bind a '
+          'function to a name.',
+      correctionMessage:
+          'Try rewriting the closure assignment as a function declaration.');
+
   PreferFunctionDeclarationsOverVariables()
       : super(
             name: 'prefer_function_declarations_over_variables',
@@ -46,6 +51,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -63,10 +71,18 @@
     if (node.initializer is FunctionExpression) {
       var function = node.thisOrAncestorOfType<FunctionBody>();
       var declaredElement = node.declaredElement;
-      if (function == null ||
-          (declaredElement != null &&
-              !function.isPotentiallyMutatedInScope(declaredElement))) {
-        rule.reportLint(node);
+      if (function == null) {
+        // When there is no enclosing function body, this is a variable
+        // definition for a field or a top-level variable, which should only
+        // be reported if final.
+        if (node.isFinal) {
+          rule.reportLint(node);
+        }
+      } else {
+        if (declaredElement != null &&
+            !function.isPotentiallyMutatedInScope(declaredElement)) {
+          rule.reportLint(node);
+        }
       }
     }
   }
diff --git a/lib/src/rules/prefer_generic_function_type_aliases.dart b/lib/src/rules/prefer_generic_function_type_aliases.dart
index f00995c..4f686ca 100644
--- a/lib/src/rules/prefer_generic_function_type_aliases.dart
+++ b/lib/src/rules/prefer_generic_function_type_aliases.dart
@@ -10,7 +10,6 @@
 const _desc = r'Prefer generic function type aliases.';
 
 const _details = r'''
-
 **PREFER** generic function type aliases.
 
 With the introduction of generic functions, function type aliases
@@ -33,8 +32,11 @@
 
 ''';
 
-class PreferGenericFunctionTypeAliases extends LintRule
-    implements NodeLintRule {
+class PreferGenericFunctionTypeAliases extends LintRule {
+  static const LintCode code = LintCode('prefer_generic_function_type_aliases',
+      "Use the generic function type syntax in 'typedef's.",
+      correctionMessage: "Try using the generic function type syntax ('{0}').");
+
   PreferGenericFunctionTypeAliases()
       : super(
             name: 'prefer_generic_function_type_aliases',
@@ -43,6 +45,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -57,6 +62,19 @@
 
   @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
-    rule.reportLint(node.name);
+    //https://github.com/dart-lang/linter/issues/2777
+    if (node.semicolon.isSynthetic) return;
+
+    var returnType = node.returnType;
+    var typeParameters = node.typeParameters;
+    var parameters = node.parameters;
+    var returnTypeSource =
+        returnType == null ? '' : '${returnType.toSource()} ';
+    var typeParameterSource =
+        typeParameters == null ? '' : typeParameters.toSource();
+    var parameterSource = parameters.toSource();
+    var replacement =
+        '${returnTypeSource}Function$typeParameterSource$parameterSource';
+    rule.reportLintForToken(node.name, arguments: [replacement]);
   }
 }
diff --git a/lib/src/rules/prefer_if_elements_to_conditional_expressions.dart b/lib/src/rules/prefer_if_elements_to_conditional_expressions.dart
index 9a2699a..ad85c23 100644
--- a/lib/src/rules/prefer_if_elements_to_conditional_expressions.dart
+++ b/lib/src/rules/prefer_if_elements_to_conditional_expressions.dart
@@ -15,33 +15,16 @@
 
 **BAD:**
 ```dart
-Widget build(BuildContext context) {
-  return Row(
-    children: [
-      IconButton(icon: Icon(Icons.menu)),
-      Expanded(child: title),
-      isAndroid ? IconButton(icon: Icon(Icons.search)) : null,
-    ].where((child) => child != null).toList(),
-  );
-}
+var list = ['a', 'b', condition ? 'c' : null].where((e) => e != null).toList();
 ```
 
 **GOOD:**
 ```dart
-Widget build(BuildContext context) {
-  return Row(
-    children: [
-      IconButton(icon: Icon(Icons.menu)),
-      Expanded(child: title),
-      if (isAndroid) IconButton(icon: Icon(Icons.search)),
-    ]
-  );
-}
+var list = ['a', 'b', if (condition) 'c'];
 ```
 ''';
 
-class PreferIfElementsToConditionalExpressions extends LintRule
-    implements NodeLintRule {
+class PreferIfElementsToConditionalExpressions extends LintRule {
   PreferIfElementsToConditionalExpressions()
       : super(
             name: 'prefer_if_elements_to_conditional_expressions',
diff --git a/lib/src/rules/prefer_if_null_operators.dart b/lib/src/rules/prefer_if_null_operators.dart
index 46855c3..d6748be 100644
--- a/lib/src/rules/prefer_if_null_operators.dart
+++ b/lib/src/rules/prefer_if_null_operators.dart
@@ -11,8 +11,7 @@
 const _desc = r'Prefer using if null operators.';
 
 const _details = r'''
-
-Prefer using if null operators instead of null checks in conditional
+**PREFER** using if null operators instead of null checks in conditional
 expressions.
 
 **BAD:**
@@ -27,7 +26,11 @@
 
 ''';
 
-class PreferIfNullOperators extends LintRule implements NodeLintRule {
+class PreferIfNullOperators extends LintRule {
+  static const LintCode code = LintCode('prefer_if_null_operators',
+      "Use the '??' operator rather than '?:' when testing for 'null'.",
+      correctionMessage: "Try rewriting the code to use '??'.");
+
   PreferIfNullOperators()
       : super(
             name: 'prefer_if_null_operators',
@@ -36,6 +39,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/prefer_initializing_formals.dart b/lib/src/rules/prefer_initializing_formals.dart
index 8bb91f9..ef287b6 100644
--- a/lib/src/rules/prefer_initializing_formals.dart
+++ b/lib/src/rules/prefer_initializing_formals.dart
@@ -7,12 +7,11 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Use initializing formals when possible.';
 
 const _details = r'''
-
 **DO** use initializing formals when possible.
 
 Using initializing formals when possible makes your code more terse.
@@ -55,7 +54,7 @@
 }
 ```
 
-**NOTE**
+**NOTE:**
 This rule will not generate a lint for named parameters unless the parameter
 name and the field name are the same. The reason for this is that resolving
 such a lint would require either renaming the field or renaming the parameter,
@@ -66,22 +65,40 @@
 class Point {
   bool isEnabled;
   Point({bool enabled}) {
-    this.isEnabled = enable; // OK
+    this.isEnabled = enabled; // OK
   }
 }
 ```
 
+**NOTE:**
+Also note that it is possible to enforce a type that is stricter than the
+initialized field with an initializing formal parameter.  In the following
+example the unnamed `Bid` constructor requires a non-null `int` despite
+`amount` being declared nullable (`int?`).
+
+```dart
+class Bid {
+ final int? amount;
+ Bid(int this.amount);
+ Bid.pass() : amount = null;
+}
+```
 ''';
 
 Iterable<AssignmentExpression> _getAssignmentExpressionsInConstructorBody(
     ConstructorDeclaration node) {
   var body = node.body;
-  var statements =
-      (body is BlockFunctionBody) ? body.block.statements : <Statement>[];
-  return statements
-      .whereType<ExpressionStatement>()
-      .map((e) => e.expression)
-      .whereType<AssignmentExpression>();
+  if (body is! BlockFunctionBody) return [];
+  var assignments = <AssignmentExpression>[];
+  for (var statement in body.block.statements) {
+    if (statement is ExpressionStatement) {
+      var expression = statement.expression;
+      if (expression is AssignmentExpression) {
+        assignments.add(expression);
+      }
+    }
+  }
+  return assignments;
 }
 
 Iterable<ConstructorFieldInitializer>
@@ -90,16 +107,20 @@
         node.initializers.whereType<ConstructorFieldInitializer>();
 
 Element? _getLeftElement(AssignmentExpression assignment) =>
-    DartTypeUtilities.getCanonicalElement(assignment.writeElement);
+    assignment.writeElement?.canonicalElement;
 
 Iterable<Element?> _getParameters(ConstructorDeclaration node) =>
-    node.parameters.parameters.map((e) => e.identifier?.staticElement);
+    node.parameters.parameters.map((e) => e.declaredElement);
 
 Element? _getRightElement(AssignmentExpression assignment) =>
-    DartTypeUtilities.getCanonicalElementFromIdentifier(
-        assignment.rightHandSide);
+    assignment.rightHandSide.canonicalElement;
 
-class PreferInitializingFormals extends LintRule implements NodeLintRule {
+class PreferInitializingFormals extends LintRule {
+  static const LintCode code = LintCode('prefer_initializing_formals',
+      'Use an initializing formal to assign a parameter to a field.',
+      correctionMessage:
+          "Try using an initialing formal ('this.{0}') to initialize the field.");
+
   PreferInitializingFormals()
       : super(
             name: 'prefer_initializing_formals',
@@ -108,6 +129,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -122,6 +146,12 @@
 
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
+    // Skip factory constructors.
+    // https://github.com/dart-lang/linter/issues/2441
+    if (node.factoryKeyword != null) {
+      return;
+    }
+
     var parameters = _getParameters(node);
     var parametersUsedOnce = <Element?>{};
     var parametersUsedMoreThanOnce = <Element?>{};
@@ -170,26 +200,40 @@
       }
     }
 
-    node.parameters.parameterElements
-        .where((p) => p != null && p.isInitializingFormal)
-        .forEach(processElement);
+    var parameterElements = node.parameters.parameterElements;
+    for (var parameter in parameterElements) {
+      if (parameter?.isInitializingFormal ?? false) {
+        processElement(parameter);
+      }
+    }
 
-    _getAssignmentExpressionsInConstructorBody(node)
-        .where(isAssignmentExpressionToLint)
-        .map(_getRightElement)
-        .forEach(processElement);
+    var assignments = _getAssignmentExpressionsInConstructorBody(node);
+    for (var assignment in assignments) {
+      if (isAssignmentExpressionToLint(assignment)) {
+        processElement(_getRightElement(assignment));
+      }
+    }
 
-    _getConstructorFieldInitializersInInitializers(node)
-        .where(isConstructorFieldInitializerToLint)
-        .map((e) => (e.expression as SimpleIdentifier).staticElement)
-        .forEach(processElement);
+    var initializers = _getConstructorFieldInitializersInInitializers(node);
+    for (var initializer in initializers) {
+      if (isConstructorFieldInitializerToLint(initializer)) {
+        processElement(
+            (initializer.expression as SimpleIdentifier).staticElement);
+      }
+    }
 
-    _getAssignmentExpressionsInConstructorBody(node)
-        .where(isAssignmentExpressionToLint)
-        .forEach(rule.reportLint);
+    for (var assignment in assignments) {
+      if (isAssignmentExpressionToLint(assignment)) {
+        var rightElement = _getRightElement(assignment)!;
+        rule.reportLint(assignment, arguments: [rightElement.displayName]);
+      }
+    }
 
-    _getConstructorFieldInitializersInInitializers(node)
-        .where(isConstructorFieldInitializerToLint)
-        .forEach(rule.reportLint);
+    for (var initializer in initializers) {
+      if (isConstructorFieldInitializerToLint(initializer)) {
+        var name = initializer.fieldName.staticElement!.name!;
+        rule.reportLint(initializer, arguments: [name]);
+      }
+    }
   }
 }
diff --git a/lib/src/rules/prefer_inlined_adds.dart b/lib/src/rules/prefer_inlined_adds.dart
index 4401d0e..2713e9b 100644
--- a/lib/src/rules/prefer_inlined_adds.dart
+++ b/lib/src/rules/prefer_inlined_adds.dart
@@ -17,17 +17,25 @@
 **BAD:**
 ```dart
 var l = ['a']..add('b')..add('c');
-var l2 = ['a']..addAll(['b', 'c'])
+var l2 = ['a']..addAll(['b', 'c']);
 ```
 
 **GOOD:**
 ```dart
 var l = ['a', 'b', 'c'];
-var 2 = ['a', 'b', 'c'];
+var l2 = ['a', 'b', 'c'];
 ```
 ''';
 
-class PreferInlinedAdds extends LintRule implements NodeLintRule {
+class PreferInlinedAdds extends LintRule {
+  static const LintCode single = LintCode(
+      'prefer_inlined_adds', 'The addition of a list item could be inlined.',
+      correctionMessage: 'Try adding the item to the list literal directly.');
+
+  static const LintCode multiple = LintCode('prefer_inlined_adds',
+      'The addition of multiple list items could be inlined.',
+      correctionMessage: 'Try adding the items to the list literal directly.');
+
   PreferInlinedAdds()
       : super(
             name: 'prefer_inlined_adds',
@@ -36,6 +44,9 @@
             group: Group.style);
 
   @override
+  List<LintCode> get lintCodes => [multiple, single];
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -61,16 +72,18 @@
     var sections = cascade?.cascadeSections;
     var target = cascade?.target;
     if (target is! ListLiteral ||
-        (sections != null && sections[0] != invocation)) {
+        (sections != null && sections.first != invocation)) {
       // todo (pq): consider extending to handle set literals.
       return;
     }
 
-    if (addAll && invocation.argumentList.arguments[0] is! ListLiteral) {
+    if (addAll && invocation.argumentList.arguments.first is! ListLiteral) {
       // Handled by: prefer_spread_collections
       return;
     }
 
-    rule.reportLint(invocation.methodName);
+    rule.reportLint(invocation.methodName,
+        errorCode:
+            addAll ? PreferInlinedAdds.multiple : PreferInlinedAdds.single);
   }
 }
diff --git a/lib/src/rules/prefer_int_literals.dart b/lib/src/rules/prefer_int_literals.dart
index dfb5785..4410e22 100644
--- a/lib/src/rules/prefer_int_literals.dart
+++ b/lib/src/rules/prefer_int_literals.dart
@@ -11,7 +11,6 @@
 const _desc = 'Prefer int literals over double literals.';
 
 const _details = '''
-
 **DO** use int literals rather than the corresponding double literal.
 
 **BAD:**
@@ -34,7 +33,7 @@
 
 ''';
 
-class PreferIntLiterals extends LintRule implements NodeLintRule {
+class PreferIntLiterals extends LintRule {
   PreferIntLiterals()
       : super(
             name: 'prefer_int_literals',
@@ -56,7 +55,6 @@
 
   /// Determine if the given literal can be replaced by an int literal.
   bool canReplaceWithIntLiteral(DoubleLiteral literal) {
-    // TODO(danrubel): Consider moving this into analyzer
     var parent = literal.parent;
     if (parent is PrefixExpression) {
       if (parent.operator.lexeme == '-') {
@@ -87,7 +85,7 @@
     } else if (parent is ListLiteral) {
       var typeArguments = parent.typeArguments?.arguments;
       return typeArguments?.length == 1 &&
-          _isDartCoreDoubleTypeAnnotation(typeArguments![0]);
+          _isDartCoreDoubleTypeAnnotation(typeArguments!.first);
     } else if (parent is NamedExpression) {
       var argList = parent.parent;
       if (argList is ArgumentList) {
diff --git a/lib/src/rules/prefer_interpolation_to_compose_strings.dart b/lib/src/rules/prefer_interpolation_to_compose_strings.dart
index 1202d89..0081a57 100644
--- a/lib/src/rules/prefer_interpolation_to_compose_strings.dart
+++ b/lib/src/rules/prefer_interpolation_to_compose_strings.dart
@@ -7,12 +7,10 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
 
 const _desc = r'Use interpolation to compose strings and values.';
 
 const _details = r'''
-
 **PREFER** using interpolation to compose strings and values.
 
 Using interpolation when composing strings and values is usually easier to write
@@ -20,18 +18,23 @@
 
 **BAD:**
 ```dart
-'Hello, ' + name + '! You are ' + (year - birth) + ' years old.';
+'Hello, ' + person.name + ' from ' + person.city + '.';
 ```
 
 **GOOD:**
 ```dart
-'Hello, $name! You are ${year - birth} years old.';
+'Hello, ${person.name} from ${person.city}.'
 ```
 
 ''';
 
-class PreferInterpolationToComposeStrings extends LintRule
-    implements NodeLintRule {
+class PreferInterpolationToComposeStrings extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_interpolation_to_compose_strings',
+      'Use interpolation to compose strings and values.',
+      correctionMessage:
+          'Try using string interpolation to build the composite string.');
+
   PreferInterpolationToComposeStrings()
       : super(
             name: 'prefer_interpolation_to_compose_strings',
@@ -40,6 +43,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -47,6 +53,18 @@
   }
 }
 
+class _NodeVisitor extends UnifyingAstVisitor {
+  Set<AstNode> skippedNodes;
+  _NodeVisitor(this.skippedNodes);
+
+  @override
+  visitNode(AstNode node) {
+    skippedNodes.add(node);
+
+    super.visitNode(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
@@ -62,22 +80,22 @@
     if (node.operator.type == TokenType.PLUS) {
       var leftOperand = node.leftOperand;
       var rightOperand = node.rightOperand;
-      //OK(#735): str1 + str2
+      // OK(#735): `str1 + str2`
       if (leftOperand is! StringLiteral && rightOperand is! StringLiteral) {
         return;
       }
-      //OK(#2490): str1 + r''
+      // OK(#2490): `str1 + r''`
       if (leftOperand is SimpleStringLiteral && leftOperand.isRaw ||
           rightOperand is SimpleStringLiteral && rightOperand.isRaw) {
         return;
       }
-      //OK: 'foo' + 'bar'
+      // OK: `'foo' + 'bar'`
       if (leftOperand is StringLiteral && rightOperand is StringLiteral) {
         return;
       }
       if (leftOperand.staticType?.isDartCoreString ?? false) {
-        DartTypeUtilities.traverseNodesInDFS(node).forEach(skippedNodes.add);
         rule.reportLint(node);
+        node.accept(_NodeVisitor(skippedNodes));
       }
     }
   }
diff --git a/lib/src/rules/prefer_is_empty.dart b/lib/src/rules/prefer_is_empty.dart
index 1679d52..0c1706a 100644
--- a/lib/src/rules/prefer_is_empty.dart
+++ b/lib/src/rules/prefer_is_empty.dart
@@ -8,17 +8,11 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../ast.dart';
+import '../extensions.dart';
 
-const alwaysFalse = 'Always false because length is always greater or equal 0.';
-
-const alwaysTrue = 'Always true because length is always greater or equal 0.';
-
-const useIsEmpty = 'Use isEmpty instead of length';
-const useIsNotEmpty = 'Use isNotEmpty instead of length';
 const _desc = r'Use `isEmpty` for Iterables and Maps.';
 const _details = r'''
-
 **DON'T** use `length` to see if a collection is empty.
 
 The `Iterable` contract does not require that a collection know its length or be
@@ -28,21 +22,46 @@
 Instead, there are faster and more readable getters: `isEmpty` and
 `isNotEmpty`.  Use the one that doesn't require you to negate the result.
 
-**GOOD:**
-```dart
-if (lunchBox.isEmpty) return 'so hungry...';
-if (words.isNotEmpty) return words.join(' ');
-```
-
 **BAD:**
 ```dart
 if (lunchBox.length == 0) return 'so hungry...';
 if (words.length != 0) return words.join(' ');
 ```
 
+**GOOD:**
+```dart
+if (lunchBox.isEmpty) return 'so hungry...';
+if (words.isNotEmpty) return words.join(' ');
+```
+
 ''';
 
-class PreferIsEmpty extends LintRule implements NodeLintRule {
+class PreferIsEmpty extends LintRule {
+  // TODO(brianwilkerson) Both `alwaysFalse` and `alwaysTrue` should be warnings
+  //  rather than lints because they represent a bug rather than a style
+  //  preference.
+  static const LintCode alwaysFalse = LintCode(
+      'prefer_is_empty',
+      "The comparison is always 'false' because the length is always greater "
+          'than or equal to 0.');
+
+  static const LintCode alwaysTrue = LintCode(
+      'prefer_is_empty',
+      "The comparison is always 'true' because the length is always greater "
+          'than or equal to 0.');
+
+  static const LintCode useIsEmpty = LintCode(
+      'prefer_is_empty',
+      "Use 'isEmpty' instead of 'length' to test whether the collection is "
+          'empty.',
+      correctionMessage: "Try rewriting the expression to use 'isEmpty'.");
+
+  static const LintCode useIsNotEmpty = LintCode(
+      'prefer_is_empty',
+      "Use 'isNotEmpty' instead of 'length' to test whether the collection is "
+          'empty.',
+      correctionMessage: "Try rewriting the expression to use 'isNotEmpty'.");
+
   PreferIsEmpty()
       : super(
             name: 'prefer_is_empty',
@@ -51,24 +70,15 @@
             group: Group.style);
 
   @override
+  List<LintCode> get lintCodes =>
+      [alwaysFalse, alwaysTrue, useIsEmpty, useIsNotEmpty];
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
     registry.addBinaryExpression(this, visitor);
   }
-
-  void reportLintWithDescription(AstNode node, String description) {
-    reporter.reportErrorForNode(_LintCode(name, description), node, []);
-  }
-}
-
-class _LintCode extends LintCode {
-  static final registry = <String, _LintCode>{};
-
-  factory _LintCode(String name, String message) =>
-      registry.putIfAbsent(name + message, () => _LintCode._(name, message));
-
-  _LintCode._(String name, String message) : super(name, message);
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
@@ -80,13 +90,15 @@
 
   @override
   void visitBinaryExpression(BinaryExpression node) {
-    var value = _getIntValue(node.rightOperand);
+    // todo(pq): not evaluating constants deliberately but we *should*.
+    // see: https://github.com/dart-lang/linter/issues/2818
+    var value = getIntValue(node.rightOperand, null);
     if (value != null) {
       if (_isLengthAccess(node.leftOperand)) {
         _check(node, value, constantOnRight: true);
       }
     } else {
-      value = _getIntValue(node.leftOperand);
+      value = getIntValue(node.leftOperand, null);
       // ignore: invariant_booleans
       if (value != null) {
         if (_isLengthAccess(node.rightOperand)) {
@@ -119,31 +131,31 @@
     if (value == 0) {
       if (operator.type == TokenType.EQ_EQ ||
           operator.type == TokenType.LT_EQ) {
-        rule.reportLintWithDescription(expression, useIsEmpty);
+        rule.reportLint(expression, errorCode: PreferIsEmpty.useIsEmpty);
       } else if (operator.type == TokenType.GT ||
           operator.type == TokenType.BANG_EQ) {
-        rule.reportLintWithDescription(expression, useIsNotEmpty);
+        rule.reportLint(expression, errorCode: PreferIsEmpty.useIsNotEmpty);
       } else if (operator.type == TokenType.LT) {
-        rule.reportLintWithDescription(expression, alwaysFalse);
+        rule.reportLint(expression, errorCode: PreferIsEmpty.alwaysFalse);
       } else if (operator.type == TokenType.GT_EQ) {
-        rule.reportLintWithDescription(expression, alwaysTrue);
+        rule.reportLint(expression, errorCode: PreferIsEmpty.alwaysTrue);
       }
     } else if (value == 1) {
       if (constantOnRight) {
         // 'length >= 1' is same as 'isNotEmpty',
         // and 'length < 1' is same as 'isEmpty'
         if (operator.type == TokenType.GT_EQ) {
-          rule.reportLintWithDescription(expression, useIsNotEmpty);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.useIsNotEmpty);
         } else if (operator.type == TokenType.LT) {
-          rule.reportLintWithDescription(expression, useIsEmpty);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.useIsEmpty);
         }
       } else {
         // '1 <= length' is same as 'isNotEmpty',
         // and '1 > length' is same as 'isEmpty'
         if (operator.type == TokenType.LT_EQ) {
-          rule.reportLintWithDescription(expression, useIsNotEmpty);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.useIsNotEmpty);
         } else if (operator.type == TokenType.GT) {
-          rule.reportLintWithDescription(expression, useIsEmpty);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.useIsEmpty);
         }
       }
     } else if (value < 0) {
@@ -152,22 +164,22 @@
         if (operator.type == TokenType.EQ_EQ ||
             operator.type == TokenType.LT_EQ ||
             operator.type == TokenType.LT) {
-          rule.reportLintWithDescription(expression, alwaysFalse);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.alwaysFalse);
         } else if (operator.type == TokenType.BANG_EQ ||
             operator.type == TokenType.GT_EQ ||
             operator.type == TokenType.GT) {
-          rule.reportLintWithDescription(expression, alwaysTrue);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.alwaysTrue);
         }
       } else {
         // 'length' is always >= 0, so comparing with negative makes no sense.
         if (operator.type == TokenType.EQ_EQ ||
             operator.type == TokenType.GT_EQ ||
             operator.type == TokenType.GT) {
-          rule.reportLintWithDescription(expression, alwaysFalse);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.alwaysFalse);
         } else if (operator.type == TokenType.BANG_EQ ||
             operator.type == TokenType.LT_EQ ||
             operator.type == TokenType.LT) {
-          rule.reportLintWithDescription(expression, alwaysTrue);
+          rule.reportLint(expression, errorCode: PreferIsEmpty.alwaysTrue);
         }
       }
     }
@@ -191,25 +203,6 @@
     return search is T ? search : null;
   }
 
-  /// Returns the value of an [IntegerLiteral] or [PrefixExpression] with a
-  /// minus and then an [IntegerLiteral]. For anything else, returns `null`.
-  int? _getIntValue(Expression expressions) {
-    if (expressions is IntegerLiteral) {
-      return expressions.value;
-    } else if (expressions is PrefixExpression) {
-      var operand = expressions.operand;
-      if (expressions.operator.type == TokenType.MINUS &&
-          operand is IntegerLiteral) {
-        var value = operand.value;
-        if (value != null) {
-          return -value;
-        }
-      }
-    }
-    // ignore: avoid_returning_null
-    return null;
-  }
-
   bool _isLengthAccess(Expression operand) {
     var node = _drillDownTo(operand, ignoreParens: true, ignoreAs: true);
     if (node == null) {
@@ -239,8 +232,8 @@
 
     // Should be subtype of Iterable, Map or String.
     if (type == null ||
-        !DartTypeUtilities.implementsInterface(type, 'Iterable', 'dart.core') &&
-            !DartTypeUtilities.implementsInterface(type, 'Map', 'dart.core') &&
+        !type.implementsInterface('Iterable', 'dart.core') &&
+            !type.implementsInterface('Map', 'dart.core') &&
             !type.isDartCoreString) {
       return false;
     }
diff --git a/lib/src/rules/prefer_is_not_empty.dart b/lib/src/rules/prefer_is_not_empty.dart
index 8430263..5b1407f 100644
--- a/lib/src/rules/prefer_is_not_empty.dart
+++ b/lib/src/rules/prefer_is_not_empty.dart
@@ -13,19 +13,11 @@
 const _desc = r'Use `isNotEmpty` for Iterables and Maps.';
 
 const _details = r'''
-
 **PREFER** `x.isNotEmpty` to `!x.isEmpty` for `Iterable` and `Map` instances.
 
 When testing whether an iterable or map is empty, prefer `isNotEmpty` over
 `!isEmpty` to improve code readability.
 
-**GOOD:**
-```dart
-if (todo.isNotEmpty) {
-  sendResults(request, todo.isEmpty);
-}
-```
-
 **BAD:**
 ```dart
 if (!sources.isEmpty) {
@@ -33,9 +25,20 @@
 }
 ```
 
+**GOOD:**
+```dart
+if (todo.isNotEmpty) {
+  sendResults(request, todo.isEmpty);
+}
+```
+
 ''';
 
-class PreferIsNotEmpty extends LintRule implements NodeLintRule {
+class PreferIsNotEmpty extends LintRule {
+  static const LintCode code = LintCode('prefer_is_not_empty',
+      "Use 'isNotEmpty' rather than negating the result of 'isEmpty'.",
+      correctionMessage: "Try rewriting the expression to use 'isNotEmpty'.");
+
   PreferIsNotEmpty()
       : super(
             name: 'prefer_is_not_empty',
@@ -44,6 +47,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/prefer_is_not_operator.dart b/lib/src/rules/prefer_is_not_operator.dart
index 7a03f0e..e36b8db 100644
--- a/lib/src/rules/prefer_is_not_operator.dart
+++ b/lib/src/rules/prefer_is_not_operator.dart
@@ -29,7 +29,14 @@
 
 ''';
 
-class PreferIsNotOperator extends LintRule implements NodeLintRule {
+class PreferIsNotOperator extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_is_not_operator',
+      "Use the 'is!' operator rather than negating the value of the 'is' "
+          'operator.',
+      correctionMessage:
+          "Try rewriting the condition to use the 'is!' operator.");
+
   PreferIsNotOperator()
       : super(
             name: 'prefer_is_not_operator',
@@ -38,6 +45,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/prefer_iterable_whereType.dart b/lib/src/rules/prefer_iterable_whereType.dart
index 7878d6d..00787c8 100644
--- a/lib/src/rules/prefer_iterable_whereType.dart
+++ b/lib/src/rules/prefer_iterable_whereType.dart
@@ -7,27 +7,30 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Prefer to use whereType on iterable.';
 
 const _details = r'''
-
 **PREFER** `iterable.whereType<T>()` over `iterable.where((e) => e is T)`.
 
 **BAD:**
 ```dart
-iterable.where((e) => e is MyClass)
+iterable.where((e) => e is MyClass);
 ```
 
 **GOOD:**
 ```dart
-iterable.whereType<MyClass>()
+iterable.whereType<MyClass>();
 ```
 
 ''';
 
-class PreferIterableWhereType extends LintRule implements NodeLintRule {
+class PreferIterableWhereType extends LintRule {
+  static const LintCode code = LintCode('prefer_iterable_whereType',
+      "Use 'whereType' to select elements of a given type.",
+      correctionMessage: "Try rewriting the expression to use 'whereType'.");
+
   PreferIterableWhereType()
       : super(
             name: 'prefer_iterable_whereType',
@@ -36,6 +39,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -53,8 +59,7 @@
     if (node.methodName.name != 'where') return;
     var target = node.realTarget;
     if (target == null ||
-        !DartTypeUtilities.implementsInterface(
-            target.staticType, 'Iterable', 'dart.core')) {
+        !target.staticType.implementsInterface('Iterable', 'dart.core')) {
       return;
     }
 
@@ -81,7 +86,7 @@
       if (expression is IsExpression && expression.notOperator == null) {
         var target = expression.expression;
         if (target is SimpleIdentifier &&
-            target.name == arg.parameters?.parameters.first.identifier?.name) {
+            target.name == arg.parameters?.parameters.first.name?.lexeme) {
           rule.reportLint(node.methodName);
         }
       }
diff --git a/lib/src/rules/prefer_mixin.dart b/lib/src/rules/prefer_mixin.dart
index 53349e7..9451ac8 100644
--- a/lib/src/rules/prefer_mixin.dart
+++ b/lib/src/rules/prefer_mixin.dart
@@ -5,9 +5,10 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _dartCollectionUri = 'dart.collection';
 const _dartConvertUri = 'dart.convert';
@@ -15,7 +16,6 @@
 const _desc = r'Prefer using mixins.';
 
 const _details = r'''
-
 Dart 2.1 introduced a new syntax for mixins that provides a safe way for a mixin
 to invoke inherited members using `super`. The new style of mixins should always
 be used for types that are to be mixed in. As a result, this lint will flag any
@@ -41,7 +41,7 @@
 const _setMixinName = 'SetMixin';
 const _stringConversionSinkName = 'StringConversionSinkMixin';
 
-class PreferMixin extends LintRule implements NodeLintRule {
+class PreferMixin extends LintRule {
   PreferMixin()
       : super(
             name: 'prefer_mixin',
@@ -64,31 +64,27 @@
 
   @override
   void visitWithClause(WithClause node) {
-    for (var type in node.mixinTypes) {
-      var element = type.name.staticElement;
-      if (element is TypeAliasElement) {
-        element = element.aliasedType.element;
-      }
-      if (element is ClassElement && !element.isMixin && !isAllowed(element)) {
-        rule.reportLint(type);
+    for (var mixinNode in node.mixinTypes) {
+      var type = mixinNode.type;
+      if (type is InterfaceType) {
+        var element = type.element;
+        if (element is! MixinElement && !isAllowed(element)) {
+          rule.reportLint(mixinNode);
+        }
       }
     }
   }
 
-  /// Check for "legacy"  classes that cannot easily be made `mixin`s for
+  /// Check for "legacy" classes that cannot easily be made `mixin`s for
   /// compatibility reasons.
   /// (See: https://github.com/dart-lang/linter/issues/2082)
-  static bool isAllowed(ClassElement element) =>
+  static bool isAllowed(InterfaceElement element) =>
       // todo (pq): remove allowlist once legacy mixins are otherwise annotated.
       // see: https://github.com/dart-lang/sdk/issues/45343
-      DartTypeUtilities.isClassElement(
-          element, _iterableMixinName, _dartCollectionUri) ||
-      DartTypeUtilities.isClassElement(
-          element, _listMixinName, _dartCollectionUri) ||
-      DartTypeUtilities.isClassElement(
-          element, _mapMixinName, _dartCollectionUri) ||
-      DartTypeUtilities.isClassElement(
-          element, _setMixinName, _dartCollectionUri) ||
-      DartTypeUtilities.isClassElement(
-          element, _stringConversionSinkName, _dartConvertUri);
+
+      element.isClass(_iterableMixinName, _dartCollectionUri) ||
+      element.isClass(_listMixinName, _dartCollectionUri) ||
+      element.isClass(_mapMixinName, _dartCollectionUri) ||
+      element.isClass(_setMixinName, _dartCollectionUri) ||
+      element.isClass(_stringConversionSinkName, _dartConvertUri);
 }
diff --git a/lib/src/rules/prefer_null_aware_method_calls.dart b/lib/src/rules/prefer_null_aware_method_calls.dart
index 9748d9a..24586ca 100644
--- a/lib/src/rules/prefer_null_aware_method_calls.dart
+++ b/lib/src/rules/prefer_null_aware_method_calls.dart
@@ -11,7 +11,6 @@
 const _desc = r'Prefer null aware method calls.';
 
 const _details = r'''
-
 Instead of checking nullability of a function/method `f` before calling it you
 can use `f?.call()`.
 
@@ -27,7 +26,7 @@
 
 ''';
 
-class PreferNullAwareMethodCalls extends LintRule implements NodeLintRule {
+class PreferNullAwareMethodCalls extends LintRule {
   PreferNullAwareMethodCalls()
       : super(
           name: 'prefer_null_aware_method_calls',
diff --git a/lib/src/rules/prefer_null_aware_operators.dart b/lib/src/rules/prefer_null_aware_operators.dart
index 4f270f9..01a5244 100644
--- a/lib/src/rules/prefer_null_aware_operators.dart
+++ b/lib/src/rules/prefer_null_aware_operators.dart
@@ -11,8 +11,7 @@
 const _desc = r'Prefer using null aware operators.';
 
 const _details = r'''
-
-Prefer using null aware operators instead of null checks in conditional
+**PREFER** using null aware operators instead of null checks in conditional
 expressions.
 
 **BAD:**
@@ -27,7 +26,13 @@
 
 ''';
 
-class PreferNullAwareOperators extends LintRule implements NodeLintRule {
+class PreferNullAwareOperators extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_null_aware_operators',
+      "Use the null-aware operator '?.' rather than an explicit 'null' "
+          'comparison.',
+      correctionMessage: "Try using '?.'.");
+
   PreferNullAwareOperators()
       : super(
             name: 'prefer_null_aware_operators',
@@ -36,6 +41,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -90,7 +98,6 @@
         }
         if (exp.toString() == expression.toString()) {
           rule.reportLint(node);
-          return;
         }
       }
     }
diff --git a/lib/src/rules/prefer_relative_imports.dart b/lib/src/rules/prefer_relative_imports.dart
index 775d66b..4d1683d 100644
--- a/lib/src/rules/prefer_relative_imports.dart
+++ b/lib/src/rules/prefer_relative_imports.dart
@@ -4,6 +4,7 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 import 'package:path/path.dart' as path;
 
 import '../analyzer.dart';
@@ -12,28 +13,31 @@
 
 const _desc = r'Prefer relative imports for files in `lib/`.';
 
-const _details = r'''Prefer relative imports for files in `lib/`.
+const _details = r'''
+**PREFER** relative imports for files in `lib/`.
 
 When mixing relative and absolute imports it's possible to create confusion
 where the same member gets imported in two different ways. One way to avoid
-that is to ensure you consistently use relative imports for files withing the
+that is to ensure you consistently use relative imports for files within the
 `lib/` directory.
 
-**GOOD:**
-
-```dart
-import 'bar.dart';
-```
-
 **BAD:**
-
 ```dart
 import 'package:my_package/bar.dart';
 ```
 
+**GOOD:**
+```dart
+import 'bar.dart';
+```
+
 ''';
 
-class PreferRelativeImports extends LintRule implements NodeLintRule {
+class PreferRelativeImports extends LintRule {
+  static const LintCode code = LintCode('prefer_relative_imports',
+      "Use relative imports for files in the 'lib' directory.",
+      correctionMessage: 'Try converting the URI to a relative URI.');
+
   PreferRelativeImports()
       : super(
             name: 'prefer_relative_imports',
@@ -42,6 +46,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     if (!isInLibDir(context.currentUnit.unit, context.package)) {
@@ -60,20 +67,22 @@
   _Visitor(this.rule, this.context);
 
   bool isPackageSelfReference(ImportDirective node) {
+    var uri = node.element?.uri;
+    if (uri is! DirectiveUriWithSource) {
+      return false;
+    }
+
     // Is it a package: import?
-    var importUriContent = node.uriContent;
-    if (importUriContent?.startsWith('package:') != true) return false;
+    var importUri = uri.relativeUri;
+    if (!importUri.isScheme('package')) return false;
 
-    var source = node.uriSource;
-    if (source == null) return false;
-
-    var importUri = node.uriSource?.uri;
     var sourceUri = node.element?.source.uri;
     if (!samePackage(importUri, sourceUri)) return false;
 
     // todo (pq): context.package.contains(source) should work (but does not)
     var packageRoot = context.package?.root;
-    return packageRoot != null && path.isWithin(packageRoot, source.fullName);
+    return packageRoot != null &&
+        path.isWithin(packageRoot, uri.source.fullName);
   }
 
   @override
diff --git a/lib/src/rules/prefer_single_quotes.dart b/lib/src/rules/prefer_single_quotes.dart
index b85642e..bbcc64a 100644
--- a/lib/src/rules/prefer_single_quotes.dart
+++ b/lib/src/rules/prefer_single_quotes.dart
@@ -10,7 +10,6 @@
 const _desc = r'Only use double quotes for strings containing single quotes.';
 
 const _details = '''
-
 **DO** use single quotes where they wouldn't require additional escapes.
 
 That means strings with an apostrophe may use double quotes so that the
@@ -43,7 +42,7 @@
 
 ''';
 
-class PreferSingleQuotes extends LintRule implements NodeLintRule {
+class PreferSingleQuotes extends LintRule {
   PreferSingleQuotes()
       : super(
             name: 'prefer_single_quotes',
diff --git a/lib/src/rules/prefer_spread_collections.dart b/lib/src/rules/prefer_spread_collections.dart
index 1cff5a0..cf5c1c7 100644
--- a/lib/src/rules/prefer_spread_collections.dart
+++ b/lib/src/rules/prefer_spread_collections.dart
@@ -11,7 +11,6 @@
 const _desc = r'Use spread collections when possible.';
 
 const _details = r'''
-
 Use spread collections when possible.
 
 Collection literals are excellent when you want to create a new collection out 
@@ -69,7 +68,12 @@
 ```
 ''';
 
-class PreferSpreadCollections extends LintRule implements NodeLintRule {
+class PreferSpreadCollections extends LintRule {
+  static const LintCode code = LintCode('prefer_spread_collections',
+      'The addition of multiple elements could be inlined.',
+      correctionMessage:
+          "Try using the spread operator ('...') to inline the addition.");
+
   PreferSpreadCollections()
       : super(
             name: 'prefer_spread_collections',
@@ -78,6 +82,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -104,11 +111,11 @@
     // todo (pq): add support for Set literals.
     if (target is! ListLiteral ||
         (target is ListLiteralImpl && target.inConstantContext) ||
-        (sections != null && sections[0] != invocation)) {
+        (sections != null && sections.first != invocation)) {
       return;
     }
 
-    var argument = invocation.argumentList.arguments[0];
+    var argument = invocation.argumentList.arguments.first;
     if (argument is ListLiteral) {
       // Handled by: prefer_inlined_adds
       return;
diff --git a/lib/src/rules/prefer_typing_uninitialized_variables.dart b/lib/src/rules/prefer_typing_uninitialized_variables.dart
index 1f7111a..ed9d034 100644
--- a/lib/src/rules/prefer_typing_uninitialized_variables.dart
+++ b/lib/src/rules/prefer_typing_uninitialized_variables.dart
@@ -10,7 +10,6 @@
 const _desc = r'Prefer typing uninitialized variables and fields.';
 
 const _details = r'''
-
 **PREFER** specifying a type annotation for uninitialized variables and fields.
 
 Forgoing type annotations for uninitialized variables is a bad practice because
@@ -56,8 +55,17 @@
 
 ''';
 
-class PreferTypingUninitializedVariables extends LintRule
-    implements NodeLintRule {
+class PreferTypingUninitializedVariables extends LintRule {
+  static const LintCode forField = LintCode(
+      'prefer_typing_uninitialized_variables',
+      'An uninitialized field should have an explicit type annotation.',
+      correctionMessage: 'Try adding a type annotation.');
+
+  static const LintCode forVariable = LintCode(
+      'prefer_typing_uninitialized_variables',
+      'An uninitialized variable should have an explicit type annotation.',
+      correctionMessage: 'Try adding a type annotation.');
+
   PreferTypingUninitializedVariables()
       : super(
             name: 'prefer_typing_uninitialized_variables',
@@ -84,9 +92,13 @@
       return;
     }
 
+    var code = node.parent is FieldDeclaration
+        ? PreferTypingUninitializedVariables.forField
+        : PreferTypingUninitializedVariables.forVariable;
+
     for (var v in node.variables) {
       if (v.initializer == null) {
-        rule.reportLint(v);
+        rule.reportLint(v, errorCode: code);
       }
     }
   }
diff --git a/lib/src/rules/prefer_void_to_null.dart b/lib/src/rules/prefer_void_to_null.dart
index 4361c5e..1293c0c 100644
--- a/lib/src/rules/prefer_void_to_null.dart
+++ b/lib/src/rules/prefer_void_to_null.dart
@@ -4,6 +4,8 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
 
@@ -11,8 +13,7 @@
     r"Don't use the Null type, unless you are positive that you don't want void.";
 
 const _details = r'''
-
-**DO NOT** use the type Null where void would work.
+**DON'T** use the type Null where void would work.
 
 **BAD:**
 ```dart
@@ -45,7 +46,11 @@
 ```
 ''';
 
-class PreferVoidToNull extends LintRule implements NodeLintRule {
+class PreferVoidToNull extends LintRule {
+  static const LintCode code = LintCode(
+      'prefer_void_to_null', "Unnecessary use of the type 'Null'.",
+      correctionMessage: "Try using 'void' instead.");
+
   PreferVoidToNull()
       : super(
             name: 'prefer_void_to_null',
@@ -54,21 +59,64 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this);
-    registry.addSimpleIdentifier(this, visitor);
-    registry.addTypeName(this, visitor);
+    var visitor = _Visitor(this, context);
+    registry.addNamedType(this, visitor);
   }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
+  final LinterContext context;
+  _Visitor(this.rule, this.context);
 
-  _Visitor(this.rule);
+  /// todo(pq): pull up to a utility.
+  ExecutableElement? getOverriddenMember(Element? member) {
+    if (member == null) {
+      return null;
+    }
+    var classElement = member.thisOrAncestorOfType<ClassElement>();
+    if (classElement == null) {
+      return null;
+    }
+    var name = member.name;
+    if (name == null) {
+      return null;
+    }
+
+    var libraryUri = classElement.library.source.uri;
+    return context.inheritanceManager.getInherited(
+      classElement.thisType,
+      Name(libraryUri, name),
+    );
+  }
+
+  bool isFutureOrVoid(DartType type) {
+    if (!type.isDartAsyncFutureOr) return false;
+    if (type is! InterfaceType) return false;
+    return type.typeArguments.first.isVoid;
+  }
+
+  bool isVoidIncompatibleOverride(MethodDeclaration parent, AstNode node) {
+    // Make sure we're checking a return type.
+    if (parent.returnType?.offset != node.offset) return false;
+
+    var member = getOverriddenMember(parent.declaredElement);
+    if (member == null) return false;
+
+    var returnType = member.returnType;
+    if (returnType.isVoid) return false;
+    if (isFutureOrVoid(returnType)) return false;
+
+    return true;
+  }
 
   @override
-  void visitTypeName(TypeName node) {
+  void visitNamedType(NamedType node) {
     var nodeType = node.type;
     if (nodeType == null || !nodeType.isDartCoreNull) {
       return;
@@ -98,6 +146,17 @@
       }
     }
 
+    // extension _ on Null {}
+    if (parent is ExtensionDeclaration) {
+      return;
+    }
+
+    // https://github.com/dart-lang/linter/issues/2792
+    if (parent is MethodDeclaration &&
+        isVoidIncompatibleOverride(parent, node)) {
+      return;
+    }
+
     rule.reportLint(node.name);
   }
 }
diff --git a/lib/src/rules/provide_deprecation_message.dart b/lib/src/rules/provide_deprecation_message.dart
index e8f9d70..2b0c54f 100644
--- a/lib/src/rules/provide_deprecation_message.dart
+++ b/lib/src/rules/provide_deprecation_message.dart
@@ -10,7 +10,6 @@
 const _desc = r'Provide a deprecation message, via @Deprecated("message").';
 
 const _details = r'''
-
 **DO** specify a deprecation message (with migration instructions and/or a
 removal schedule) in the Deprecation constructor.
 
@@ -32,7 +31,12 @@
 
 ''';
 
-class ProvideDeprecationMessage extends LintRule implements NodeLintRule {
+class ProvideDeprecationMessage extends LintRule {
+  static const LintCode code = LintCode(
+      'provide_deprecation_message', 'Missing a deprecation message.',
+      correctionMessage:
+          "Try using the constructor to provide a message ('@Deprecated(\"message\")').");
+
   ProvideDeprecationMessage()
       : super(
             name: 'provide_deprecation_message',
@@ -41,6 +45,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/pub/package_names.dart b/lib/src/rules/pub/package_names.dart
index 5dc0954..f452a02 100644
--- a/lib/src/rules/pub/package_names.dart
+++ b/lib/src/rules/pub/package_names.dart
@@ -8,7 +8,6 @@
 const _desc = r'Use `lowercase_with_underscores` for package names.';
 
 const _details = r'''
-
 From the [Pubspec format description](https://dart.dev/tools/pub/pubspec):
 
 **DO** use `lowercase_with_underscores` for package names.
@@ -21,6 +20,11 @@
 ''';
 
 class PubPackageNames extends LintRule {
+  static const LintCode code = LintCode(
+      'package_names', "The package name '{0}' isn't a snake_case identifier.",
+      correctionMessage:
+          'Try changing the name to follow the snake_case style.');
+
   PubPackageNames()
       : super(
             name: 'package_names',
@@ -29,6 +33,9 @@
             group: Group.pub);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   PubspecVisitor getPubspecVisitor() => Visitor(this);
 }
 
@@ -41,7 +48,7 @@
   void visitPackageName(PSEntry name) {
     var packageName = name.value.text;
     if (packageName != null && !isValidPackageName(packageName)) {
-      rule.reportPubLint(name.value);
+      rule.reportPubLint(name.value, arguments: [packageName]);
     }
   }
 }
diff --git a/lib/src/rules/pub/secure_pubspec_urls.dart b/lib/src/rules/pub/secure_pubspec_urls.dart
new file mode 100644
index 0000000..09c475c
--- /dev/null
+++ b/lib/src/rules/pub/secure_pubspec_urls.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/lint/pub.dart'; // ignore: implementation_imports
+
+import '../../analyzer.dart';
+
+const _desc = r'Use secure urls in `pubspec.yaml`.';
+
+const _details = r'''
+**DO** Use secure urls in `pubspec.yaml`.
+
+Use `https` instead of `http` or `git:`.
+
+**BAD:**
+```yaml
+repository: http://github.com/dart-lang/example
+```
+
+```yaml
+git:
+  url: git://github.com/dart-lang/example/example.git
+```
+
+**GOOD:**
+```yaml
+repository: https://github.com/dart-lang/example
+```
+
+''';
+
+class SecurePubspecUrls extends LintRule {
+  static const LintCode code = LintCode('secure_pubspec_urls',
+      "The '{0}' protocol shouldn't be used because it isn't secure.",
+      correctionMessage: "Try using a secure protocol, such as 'https'.");
+
+  SecurePubspecUrls()
+      : super(
+            name: 'secure_pubspec_urls',
+            description: _desc,
+            details: _details,
+            group: Group.pub);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  PubspecVisitor getPubspecVisitor() => Visitor(this);
+}
+
+class Visitor extends PubspecVisitor<void> {
+  final LintRule rule;
+
+  Visitor(this.rule);
+
+  @override
+  void visitPackageDependencies(PSDependencyList dependencies) {
+    _visitDeps(dependencies);
+  }
+
+  @override
+  void visitPackageDependencyOverrides(PSDependencyList dependencies) {
+    _visitDeps(dependencies);
+  }
+
+  @override
+  void visitPackageDevDependencies(PSDependencyList dependencies) {
+    _visitDeps(dependencies);
+  }
+
+  @override
+  void visitPackageDocumentation(PSEntry documentation) {
+    _checkUrl(documentation.value);
+  }
+
+  @override
+  void visitPackageHomepage(PSEntry homepage) {
+    _checkUrl(homepage.value);
+  }
+
+  @override
+  void visitPackageIssueTracker(PSEntry issueTracker) {
+    _checkUrl(issueTracker.value);
+  }
+
+  @override
+  void visitPackageRepository(PSEntry repostory) {
+    _checkUrl(repostory.value);
+  }
+
+  _checkUrl(PSNode? node) {
+    if (node == null) return;
+    var text = node.text;
+    if (text != null) {
+      var uri = Uri.tryParse(text);
+      if (uri != null && (uri.isScheme('http') || uri.isScheme('git'))) {
+        rule.reportPubLint(node, arguments: [uri.scheme]);
+      }
+    }
+  }
+
+  void _visitDeps(PSDependencyList dependencies) {
+    for (var dep in dependencies) {
+      _checkUrl(dep.git?.url?.value);
+      _checkUrl(dep.host?.url?.value);
+    }
+  }
+}
diff --git a/lib/src/rules/pub/sort_pub_dependencies.dart b/lib/src/rules/pub/sort_pub_dependencies.dart
index 24becec..f5e8887 100644
--- a/lib/src/rules/pub/sort_pub_dependencies.dart
+++ b/lib/src/rules/pub/sort_pub_dependencies.dart
@@ -7,15 +7,19 @@
 
 import '../../analyzer.dart';
 
-const _desc = r'Sort pub dependencies.';
+const _desc = r'Sort pub dependencies alphabetically.';
 
 const _details = r'''
-**DO** sort pub dependencies in `pubspec.yaml`.
+**DO** sort pub dependencies alphabetically (A to Z) in `pubspec.yaml`.
 
 Sorting list of pub dependencies makes maintenance easier.
 ''';
 
 class SortPubDependencies extends LintRule {
+  static const LintCode code = LintCode(
+      'sort_pub_dependencies', 'Unsorted dependencies.',
+      correctionMessage: 'Try sorting the dependencies.');
+
   SortPubDependencies()
       : super(
             name: 'sort_pub_dependencies',
@@ -24,6 +28,9 @@
             group: Group.pub);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   PubspecVisitor getPubspecVisitor() => Visitor(this);
 }
 
@@ -38,12 +45,12 @@
   }
 
   @override
-  void visitPackageDevDependencies(PSDependencyList dependencies) {
+  void visitPackageDependencyOverrides(PSDependencyList dependencies) {
     _visitDeps(dependencies);
   }
 
   @override
-  void visitPackageDependencyOverrides(PSDependencyList dependencies) {
+  void visitPackageDevDependencies(PSDependencyList dependencies) {
     _visitDeps(dependencies);
   }
 
diff --git a/lib/src/rules/public_member_api_docs.dart b/lib/src/rules/public_member_api_docs.dart
index 6d9d4b2..53d613b 100644
--- a/lib/src/rules/public_member_api_docs.dart
+++ b/lib/src/rules/public_member_api_docs.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 
@@ -12,12 +13,18 @@
 const _desc = r'Document all public members.';
 
 const _details = r'''
-
 **DO** document all public members.
 
 All non-overriding public members should be documented with `///` doc-style
 comments.
 
+**BAD:**
+```dart
+class Bad {
+  void meh() { }
+}
+```
+
 **GOOD:**
 ```dart
 /// A good thing.
@@ -29,13 +36,6 @@
 }
 ```
 
-**BAD:**
-```dart
-class Bad {
-  void meh() { }
-}
-```
-
 In case a public member overrides a member it is up to the declaring member
 to provide documentation.  For example, in the following, `Sub` needn't
 document `init` (though it certainly may, if there's need).
@@ -55,7 +55,7 @@
 }
 ```
 
-Note that consistent with `dartdoc`, an exception to the rule is made when
+Note that consistent with `dart doc`, an exception to the rule is made when
 documented getters have corresponding undocumented setters.  In this case the
 setters inherit the docs from the getters.
 
@@ -67,7 +67,7 @@
 // of the actual API surface area of a package - including that defined by
 // exports - and linting against that.
 
-class PublicMemberApiDocs extends LintRule implements NodeLintRule {
+class PublicMemberApiDocs extends LintRule {
   PublicMemberApiDocs()
       : super(
             name: 'public_member_api_docs',
@@ -106,19 +106,62 @@
 
   bool check(Declaration node) {
     if (node.documentationComment == null && !isOverridingMember(node)) {
-      rule.reportLint(getNodeToAnnotate(node));
+      var errorNode = getNodeToAnnotate(node);
+      rule.reportLintForOffset(errorNode.offset, errorNode.length);
       return true;
     }
     return false;
   }
 
+  void checkMethods(List<ClassMember> members) {
+    // Check methods
+
+    var getters = <String, MethodDeclaration>{};
+    var setters = <MethodDeclaration>[];
+
+    // Non-getters/setters.
+    var methods = <MethodDeclaration>[];
+
+    // Identify getter/setter pairs.
+    for (var member in members) {
+      if (member is MethodDeclaration && !isPrivate(member.name)) {
+        if (member.isGetter) {
+          getters[member.name.lexeme] = member;
+        } else if (member.isSetter) {
+          setters.add(member);
+        } else {
+          methods.add(member);
+        }
+      }
+    }
+
+    // Check all getters, and collect offenders along the way.
+    var missingDocs = <MethodDeclaration>{};
+    for (var getter in getters.values) {
+      if (check(getter)) {
+        missingDocs.add(getter);
+      }
+    }
+
+    // But only setters whose getter is missing a doc.
+    for (var setter in setters) {
+      var getter = getters[setter.name.lexeme];
+      if (getter != null && missingDocs.contains(getter)) {
+        check(setter);
+      }
+    }
+
+    // Check remaining methods.
+    methods.forEach(check);
+  }
+
   Element? getOverriddenMember(Element? member) {
     if (member == null) {
       return null;
     }
 
-    var classElement = member.thisOrAncestorOfType<ClassElement>();
-    if (classElement == null) {
+    var interfaceElement = member.thisOrAncestorOfType<InterfaceElement>();
+    if (interfaceElement == null) {
       return null;
     }
     var name = member.name;
@@ -126,9 +169,9 @@
       return null;
     }
 
-    var libraryUri = classElement.library.source.uri;
+    var libraryUri = interfaceElement.library.source.uri;
     return context.inheritanceManager.getInherited(
-      classElement.thisType,
+      interfaceElement.thisType,
       Name(libraryUri, name),
     );
   }
@@ -138,7 +181,7 @@
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
-    _visitClassOrMixin(node);
+    _visitMembers(node, node.name, node.members);
   }
 
   @override
@@ -162,9 +205,9 @@
     for (var member in node.declarations) {
       if (member is FunctionDeclaration) {
         var name = member.name;
-        if (!isPrivate(name) && name.name != 'main') {
+        if (!isPrivate(name) && name.lexeme != 'main') {
           if (member.isGetter) {
-            getters[member.name.name] = member;
+            getters[member.name.lexeme] = member;
           } else if (member.isSetter) {
             setters.add(member);
           } else {
@@ -184,7 +227,7 @@
 
     // But only setters whose getter is missing a doc.
     for (var setter in setters) {
-      var getter = getters[setter.name.name];
+      var getter = getters[setter.name.lexeme];
       if (getter != null && missingDocs.contains(getter)) {
         check(setter);
       }
@@ -212,9 +255,10 @@
 
   @override
   void visitEnumDeclaration(EnumDeclaration node) {
-    if (!isPrivate(node.name)) {
-      check(node);
-    }
+    if (isPrivate(node.name)) return;
+
+    check(node);
+    checkMethods(node.members);
   }
 
   @override
@@ -224,46 +268,7 @@
     }
 
     check(node);
-
-    // Check methods
-
-    var getters = <String, MethodDeclaration>{};
-    var setters = <MethodDeclaration>[];
-
-    // Non-getters/setters.
-    var methods = <MethodDeclaration>[];
-
-    // Identify getter/setter pairs.
-    for (var member in node.members) {
-      if (member is MethodDeclaration && !isPrivate(member.name)) {
-        if (member.isGetter) {
-          getters[member.name.name] = member;
-        } else if (member.isSetter) {
-          setters.add(member);
-        } else {
-          methods.add(member);
-        }
-      }
-    }
-
-    // Check all getters, and collect offenders along the way.
-    var missingDocs = <MethodDeclaration>{};
-    for (var getter in getters.values) {
-      if (check(getter)) {
-        missingDocs.add(getter);
-      }
-    }
-
-    // But only setters whose getter is missing a doc.
-    for (var setter in setters) {
-      var getter = getters[setter.name.name];
-      if (getter != null && missingDocs.contains(getter)) {
-        check(setter);
-      }
-    }
-
-    // Check remaining methods.
-    methods.forEach(check);
+    checkMethods(node.members);
   }
 
   @override
@@ -293,7 +298,7 @@
 
   @override
   void visitMixinDeclaration(MixinDeclaration node) {
-    _visitClassOrMixin(node);
+    _visitMembers(node, node.name, node.members);
   }
 
   @override
@@ -305,67 +310,10 @@
     }
   }
 
-  void _visitClassOrMixin(ClassOrMixinDeclaration node) {
-    if (isPrivate(node.name)) return;
+  void _visitMembers(Declaration node, Token name, List<ClassMember> members) {
+    if (isPrivate(name)) return;
 
     check(node);
-
-    // Check methods
-
-    var getters = <String, MethodDeclaration>{};
-    var setters = <MethodDeclaration>[];
-
-    // Non-getters/setters.
-    var methods = <MethodDeclaration>[];
-
-    // Identify getter/setter pairs.
-    for (var member in node.members) {
-      if (member is MethodDeclaration && !isPrivate(member.name)) {
-        if (member.isGetter) {
-          getters[member.name.name] = member;
-        } else if (member.isSetter) {
-          setters.add(member);
-        } else {
-          methods.add(member);
-        }
-      }
-    }
-
-    // Check all getters, and collect offenders along the way.
-    var missingDocs = <MethodDeclaration>{};
-    for (var getter in getters.values) {
-      if (check(getter)) {
-        missingDocs.add(getter);
-      }
-    }
-
-    var declaredElement = node.declaredElement;
-    if (declaredElement == null) {
-      return;
-    }
-
-    // But only setters whose getter is missing a doc.
-    for (var setter in setters) {
-      var getter = getters[setter.name.name];
-      if (getter == null) {
-        var libraryUri = declaredElement.library.source.uri;
-        // Look for an inherited getter.
-        Element? getter = context.inheritanceManager.getMember(
-          declaredElement.thisType,
-          Name(libraryUri, setter.name.name),
-        );
-        if (getter is PropertyAccessorElement) {
-          if (getter.documentationComment != null) {
-            continue;
-          }
-        }
-        check(setter);
-      } else if (missingDocs.contains(getter)) {
-        check(setter);
-      }
-    }
-
-    // Check remaining methods.
-    methods.forEach(check);
+    checkMethods(members);
   }
 }
diff --git a/lib/src/rules/recursive_getters.dart b/lib/src/rules/recursive_getters.dart
index fa2ba1c..e0579e6 100644
--- a/lib/src/rules/recursive_getters.dart
+++ b/lib/src/rules/recursive_getters.dart
@@ -7,12 +7,10 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
 
 const _desc = r'Property getter recursively returns itself.';
 
 const _details = r'''
-
 **DON'T** create recursive getters.
 
 Recursive getters are getters which return themselves as a value.  This is
@@ -37,7 +35,11 @@
 
 ''';
 
-class RecursiveGetters extends LintRule implements NodeLintRule {
+class RecursiveGetters extends LintRule {
+  static const LintCode code = LintCode(
+      'recursive_getters', "The getter '{0}' recursively returns itself.",
+      correctionMessage: 'Try changing the value being returned.');
+
   RecursiveGetters()
       : super(
             name: 'recursive_getters',
@@ -46,6 +48,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -54,66 +59,66 @@
   }
 }
 
-/// Tests if a simple identifier is a recursive getter by looking at its parent.
-class _RecursiveGetterParentVisitor extends SimpleAstVisitor<bool> {
-  @override
-  bool visitPropertyAccess(PropertyAccess node) =>
-      node.target is ThisExpression;
+class _BodyVisitor extends RecursiveAstVisitor {
+  final LintRule rule;
+  final ExecutableElement element;
+  _BodyVisitor(this.element, this.rule);
 
-  @override
-  bool? visitSimpleIdentifier(SimpleIdentifier node) {
+  bool isSelfReference(SimpleIdentifier node) {
+    if (node.staticElement != element) return false;
     var parent = node.parent;
-    if (parent is ArgumentList ||
-        parent is ConditionalExpression ||
-        parent is ExpressionFunctionBody ||
-        parent is ReturnStatement) {
-      return true;
+    if (parent is PrefixedIdentifier) return false;
+    if (parent is PropertyAccess && parent.target is! ThisExpression) {
+      return false;
+    }
+    return true;
+  }
+
+  @override
+  visitListLiteral(ListLiteral node) {
+    if (node.isConst) return null;
+    return super.visitListLiteral(node);
+  }
+
+  @override
+  visitSetOrMapLiteral(SetOrMapLiteral node) {
+    if (node.isConst) return null;
+    return super.visitSetOrMapLiteral(node);
+  }
+
+  @override
+  visitSimpleIdentifier(SimpleIdentifier node) {
+    if (isSelfReference(node)) {
+      rule.reportLint(node, arguments: [node.name]);
     }
 
-    if (parent is PropertyAccess) {
-      return parent.accept(this);
-    }
-
-    return false;
+    // No need to call super visit (SimpleIdentifiers have no children).
   }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
-  final visitor = _RecursiveGetterParentVisitor();
-
   _Visitor(this.rule);
 
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
     // getters have null arguments, methods have parameters, could be empty.
-    if (node.functionExpression.parameters != null) {
-      return;
-    }
+    if (node.functionExpression.parameters != null) return;
 
-    var element = node.declaredElement;
-    _verifyElement(node.functionExpression, element);
+    _verifyElement(node.functionExpression, node.declaredElement);
   }
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
     // getters have null arguments, methods have parameters, could be empty.
-    if (node.parameters != null) {
-      return;
-    }
+    if (node.parameters != null) return;
 
-    var element = node.declaredElement;
-    _verifyElement(node.body, element);
+    _verifyElement(node.body, node.declaredElement);
   }
 
   void _verifyElement(AstNode node, ExecutableElement? element) {
-    var nodes = DartTypeUtilities.traverseNodesInDFS(node);
-    nodes
-        .where((n) =>
-            n is SimpleIdentifier &&
-            element == n.staticElement &&
-            (n.accept(visitor) ?? false))
-        .forEach(rule.reportLint);
+    if (element == null) return;
+    node.accept(_BodyVisitor(element, rule));
   }
 }
diff --git a/lib/src/rules/require_trailing_commas.dart b/lib/src/rules/require_trailing_commas.dart
index 45fddd1..4d77e12 100644
--- a/lib/src/rules/require_trailing_commas.dart
+++ b/lib/src/rules/require_trailing_commas.dart
@@ -11,11 +11,18 @@
 const _desc = r'Use trailing commas for all function calls and declarations.';
 
 const _details = r'''
-
 **DO** use trailing commas for all function calls and declarations unless the
 function call or definition, from the start of the function name up to the
 closing parenthesis, fits in a single line.
 
+**BAD:**
+```dart
+void run() {
+  method('does not fit on one line',
+      'test test test test test test test test test test test');
+}
+```
+
 **GOOD:**
 ```dart
 void run() {
@@ -26,25 +33,17 @@
 }
 ```
 
-**BAD:**
-```dart
-void run() {
-  method('does not fit on one line',
-      'test test test test test test test test test test test');
-}
-```
-
-**Exception:** If the final parameter/argument is positional (vs named) and is
+**EXCEPTION:** If the final parameter/argument is positional (vs named) and is
 either a function literal implemented using curly braces, a literal map, a
 literal set or a literal array. This exception only applies if the final
 parameter does not fit entirely on one line.
 
-**Note:** This lint rule assumes `dartfmt` has been run over the code and may
-produce false positives until that has happened.
+**NOTE:** This lint rule assumes `dart format` has been run over the code and
+may produce false positives until that has happened.
 
 ''';
 
-class RequireTrailingCommas extends LintRule implements NodeLintRule {
+class RequireTrailingCommas extends LintRule {
   RequireTrailingCommas()
       : super(
           name: 'require_trailing_commas',
@@ -77,7 +76,7 @@
 
   final LintRule rule;
 
-  LineInfo? _lineInfo;
+  late LineInfo _lineInfo;
 
   _Visitor(this.rule);
 
@@ -136,8 +135,9 @@
 
     // No trailing comma is needed if the function call or declaration, up to
     // the closing parenthesis, fits on a single line. Ensuring the left and
-    // right parenthesis are on the same line is sufficient since dartfmt places
-    // the left parenthesis right after the identifier (on the same line).
+    // right parenthesis are on the same line is sufficient since `dart format`
+    // places the left parenthesis right after the identifier (on the same
+    // line).
     if (_isSameLine(leftParenthesis, rightParenthesis)) return;
 
     // Check the last parameter to determine if there are any exceptions.
@@ -147,22 +147,34 @@
   }
 
   bool _isSameLine(Token token1, Token token2) =>
-      _lineInfo!.getLocation(token1.offset).lineNumber ==
-      _lineInfo!.getLocation(token2.offset).lineNumber;
+      _lineInfo.getLocation(token1.offset).lineNumber ==
+      _lineInfo.getLocation(token2.end).lineNumber;
 
   bool _shouldAllowTrailingCommaException(AstNode lastNode) {
-    // No exceptions are allowed if the last parameter is named.
+    // No exceptions are allowed if the last argument is named.
     if (lastNode is FormalParameter && lastNode.isNamed) return false;
 
-    // No exceptions are allowed if the entire last parameter fits on one line.
+    // No exceptions are allowed if the entire last argument fits on one line.
     if (_isSameLine(lastNode.beginToken, lastNode.endToken)) return false;
 
-    // Exception is allowed if the last parameter is a function literal.
+    // Exception is allowed if the last argument is a function literal.
     if (lastNode is FunctionExpression && lastNode.body is BlockFunctionBody) {
       return true;
     }
 
-    // Exception is allowed if the last parameter is a set, map or list literal.
+    // Exception is allowed if the last argument is a (multiline) string literal.
+    if (lastNode is StringLiteral) return true;
+
+    // Exception is allowed if the last argument is a anonymous function call.
+    // This case arises a lot in asserts.
+    if (lastNode is FunctionExpressionInvocation &&
+        lastNode.function is FunctionExpression &&
+        _isSameLine(lastNode.argumentList.leftParenthesis,
+            lastNode.argumentList.rightParenthesis)) {
+      return true;
+    }
+
+    // Exception is allowed if the last argument is a set, map or list literal.
     if (lastNode is SetOrMapLiteral || lastNode is ListLiteral) return true;
 
     return false;
diff --git a/lib/src/rules/sized_box_for_whitespace.dart b/lib/src/rules/sized_box_for_whitespace.dart
index 45a8179..771c006 100644
--- a/lib/src/rules/sized_box_for_whitespace.dart
+++ b/lib/src/rules/sized_box_for_whitespace.dart
@@ -10,7 +10,8 @@
 
 const _desc = r'SizedBox for whitespace.';
 
-const _details = r'''Use SizedBox to add whitespace to a layout.
+const _details = r'''
+Use SizedBox to add whitespace to a layout.
 
 A `Container` is a heavier Widget than a `SizedBox`, and as bonus, `SizedBox`
 has a `const` constructor.
@@ -46,7 +47,11 @@
 ```
 ''';
 
-class SizedBoxForWhitespace extends LintRule implements NodeLintRule {
+class SizedBoxForWhitespace extends LintRule {
+  static const LintCode code = LintCode('sized_box_for_whitespace',
+      "Use a 'SizedBox' to add whitespace to a layout.",
+      correctionMessage: "Try using a 'SizedBox' rather than a 'Container'.");
+
   SizedBoxForWhitespace()
       : super(
             name: 'sized_box_for_whitespace',
@@ -55,6 +60,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -63,6 +71,35 @@
   }
 }
 
+class _ArgumentData {
+  var incompatibleParamsFound = false;
+
+  var positionalArgumentFound = false;
+  var seenWidth = false;
+  var seenHeight = false;
+  var seenChild = false;
+  _ArgumentData(ArgumentList node) {
+    for (var argument in node.arguments) {
+      if (argument is! NamedExpression) {
+        positionalArgumentFound = true;
+        return;
+      }
+      var label = argument.name.label;
+      if (label.name == 'width') {
+        seenWidth = true;
+      } else if (label.name == 'height') {
+        seenHeight = true;
+      } else if (label.name == 'child') {
+        seenChild = true;
+      } else if (label.name == 'key') {
+        // key doesn't matter (both SizedBox and Container have it)
+      } else {
+        incompatibleParamsFound = true;
+      }
+    }
+  }
+}
+
 class _Visitor extends SimpleAstVisitor {
   final LintRule rule;
 
@@ -74,40 +111,14 @@
       return;
     }
 
-    var visitor = _WidthOrHeightArgumentVisitor();
-    node.visitChildren(visitor);
-    if (visitor.seenIncompatibleParams) {
+    var data = _ArgumentData(node.argumentList);
+
+    if (data.incompatibleParamsFound || data.positionalArgumentFound) {
       return;
     }
-    if (visitor.seenChild && (visitor.seenWidth || visitor.seenHeight) ||
-        visitor.seenWidth && visitor.seenHeight) {
+    if (data.seenChild && (data.seenWidth || data.seenHeight) ||
+        data.seenWidth && data.seenHeight) {
       rule.reportLint(node.constructorName);
     }
   }
 }
-
-class _WidthOrHeightArgumentVisitor extends SimpleAstVisitor<void> {
-  var seenWidth = false;
-  var seenHeight = false;
-  var seenChild = false;
-  var seenIncompatibleParams = false;
-
-  @override
-  void visitArgumentList(ArgumentList node) {
-    for (var name in node.arguments
-        .cast<NamedExpression>()
-        .map((arg) => arg.name.label.name)) {
-      if (name == 'width') {
-        seenWidth = true;
-      } else if (name == 'height') {
-        seenHeight = true;
-      } else if (name == 'child') {
-        seenChild = true;
-      } else if (name == 'key') {
-        // key doesn't matter (both SiezdBox and Container have it)
-      } else {
-        seenIncompatibleParams = true;
-      }
-    }
-  }
-}
diff --git a/lib/src/rules/sized_box_shrink_expand.dart b/lib/src/rules/sized_box_shrink_expand.dart
new file mode 100644
index 0000000..3b5fa49
--- /dev/null
+++ b/lib/src/rules/sized_box_shrink_expand.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+import '../util/flutter_utils.dart';
+
+const _details =
+    r'''Use `SizedBox.shrink(...)` and `SizedBox.expand(...)` constructors appropriately.
+
+The `SizedBox.shrink(...)` and `SizedBox.expand(...)` constructors should be used
+instead of the more general `SizedBox(...)` constructor when the named constructors
+capture the intent of the code more succinctly. 
+
+**Examples**
+
+**BAD:**
+```dart
+Widget buildLogo() {
+  return SizedBox(
+    height: 0,
+    width: 0,
+    child: const MyLogo(),
+  );
+}
+```
+
+```dart
+Widget buildLogo() {
+  return SizedBox(
+    height: double.infinity,
+    width: double.infinity,
+    child: const MyLogo(),
+  );
+}
+```
+
+**GOOD:**
+```dart
+Widget buildLogo() {
+  return SizedBox.shrink(
+    child: const MyLogo(),
+  );
+}
+```
+
+```dart
+Widget buildLogo() {
+  return SizedBox.expand(
+    child: const MyLogo(),
+  );
+}
+```
+''';
+
+class SizedBoxShrinkExpand extends LintRule {
+  SizedBoxShrinkExpand()
+      : super(
+            name: 'sized_box_shrink_expand',
+            description: 'Use SizedBox shrink and expand named constructors.',
+            details: _details,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+
+    registry.addInstanceCreationExpression(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final SizedBoxShrinkExpand rule;
+
+  _Visitor(this.rule);
+
+  static const LintCode useShrink = LintCode(
+      'sized_box_shrink_expand', 'Use the `SizedBox.shrink` constructor.');
+  static const LintCode useExpand = LintCode(
+      'sized_box_shrink_expand', 'Use the `SizedBox.expand` constructor.');
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    // Only interested in the default constructor for the SizedBox widget
+    if (!isExactWidgetTypeSizedBox(node.staticType) ||
+        node.constructorName.name != null) {
+      return;
+    }
+
+    var data = _ArgumentData(node.argumentList);
+    if (data.positionalArgumentFound) {
+      return;
+    }
+    if (data.width == 0 && data.height == 0) {
+      rule.reportLint(node.constructorName, errorCode: useShrink);
+    } else if (data.width == double.infinity &&
+        data.height == double.infinity) {
+      rule.reportLint(node.constructorName, errorCode: useExpand);
+    }
+  }
+}
+
+class _ArgumentData {
+  _ArgumentData(ArgumentList node) {
+    for (var argument in node.arguments) {
+      if (argument is! NamedExpression) {
+        positionalArgumentFound = true;
+        return;
+      }
+      var label = argument.name.label;
+      if (label.name == 'width') {
+        width = _argumentValue(argument.expression);
+      } else if (label.name == 'height') {
+        height = _argumentValue(argument.expression);
+      }
+    }
+  }
+
+  double? _argumentValue(Expression argument) {
+    if (argument is IntegerLiteral) {
+      return argument.value?.toDouble();
+    } else if (argument is DoubleLiteral) {
+      return argument.value;
+    } else if (argument is PrefixedIdentifier &&
+        argument.identifier.name == 'infinity' &&
+        argument.prefix.name == 'double') {
+      return double.infinity;
+    }
+    return null;
+  }
+
+  var positionalArgumentFound = false;
+  double? width;
+  double? height;
+}
diff --git a/lib/src/rules/slash_for_doc_comments.dart b/lib/src/rules/slash_for_doc_comments.dart
index 7216916..6a4c1f6 100644
--- a/lib/src/rules/slash_for_doc_comments.dart
+++ b/lib/src/rules/slash_for_doc_comments.dart
@@ -10,7 +10,6 @@
 const _desc = r'Prefer using /// for doc comments.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style):
 
 **PREFER** using `///` for doc comments.
@@ -40,10 +39,14 @@
     return false;
   }
   //Should be only one
-  return comment.tokens[0].lexeme.startsWith('/**');
+  return comment.tokens.first.lexeme.startsWith('/**');
 }
 
-class SlashForDocComments extends LintRule implements NodeLintRule {
+class SlashForDocComments extends LintRule {
+  static const LintCode code = LintCode('slash_for_doc_comments',
+      "Use the end-of-line form ('///') for doc comments.",
+      correctionMessage: "Try rewriting the comment to use '///'.");
+
   SlashForDocComments()
       : super(
             name: 'slash_for_doc_comments',
@@ -52,6 +55,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -98,7 +104,7 @@
   void visitCompilationUnit(CompilationUnit node) {
     var directives = node.directives;
     if (directives.isNotEmpty) {
-      checkComment(directives[0].documentationComment);
+      checkComment(directives.first.documentationComment);
     }
   }
 
diff --git a/lib/src/rules/sort_child_properties_last.dart b/lib/src/rules/sort_child_properties_last.dart
index 3e34a3d..e4a3ef4 100644
--- a/lib/src/rules/sort_child_properties_last.dart
+++ b/lib/src/rules/sort_child_properties_last.dart
@@ -80,7 +80,12 @@
 
 ''';
 
-class SortChildPropertiesLast extends LintRule implements NodeLintRule {
+class SortChildPropertiesLast extends LintRule {
+  static const LintCode code = LintCode('sort_child_properties_last',
+      "The '{0}' argument should be last in widget constructor invocations.",
+      correctionMessage:
+          'Try moving the argument to the end of the argument list.');
+
   SortChildPropertiesLast()
       : super(
             name: 'sort_child_properties_last',
@@ -89,6 +94,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -123,7 +131,9 @@
             element.expression is! FunctionExpression)
         .isEmpty;
     if (!onlyClosuresAfterChild) {
-      rule.reportLint(arguments.firstWhere(isChildArg));
+      var argument = arguments.firstWhere(isChildArg);
+      var name = (argument as NamedExpression).name.label.name;
+      rule.reportLint(argument, arguments: [name]);
     }
   }
 
diff --git a/lib/src/rules/sort_constructors_first.dart b/lib/src/rules/sort_constructors_first.dart
index 5485b48..c008263 100644
--- a/lib/src/rules/sort_constructors_first.dart
+++ b/lib/src/rules/sort_constructors_first.dart
@@ -10,18 +10,8 @@
 const _desc = r'Sort constructor declarations before other members.';
 
 const _details = r'''
-
 **DO** sort constructor declarations before other members.
 
-**GOOD:**
-```dart
-abstract class Animation<T> {
-  const Animation(this.value);
-  double value;
-  void addListener(VoidCallback listener);
-}
-```
-
 **BAD:**
 ```dart
 abstract class Visitor {
@@ -31,9 +21,18 @@
 }
 ```
 
+**GOOD:**
+```dart
+abstract class Animation<T> {
+  const Animation(this.value);
+  double value;
+  void addListener(VoidCallback listener);
+}
+```
+
 ''';
 
-class SortConstructorsFirst extends LintRule implements NodeLintRule {
+class SortConstructorsFirst extends LintRule {
   SortConstructorsFirst()
       : super(
             name: 'sort_constructors_first',
@@ -46,6 +45,7 @@
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
     registry.addClassDeclaration(this, visitor);
+    registry.addEnumDeclaration(this, visitor);
   }
 }
 
@@ -54,13 +54,9 @@
 
   _Visitor(this.rule);
 
-  @override
-  void visitClassDeclaration(ClassDeclaration node) {
-    // Sort members by offset.
-    var members = node.members.toList()
-      ..sort((ClassMember m1, ClassMember m2) => m1.offset - m2.offset);
-
+  void check(NodeList<ClassMember> members) {
     var other = false;
+    // Members are sorted by source position in the AST.
     for (var member in members) {
       if (member is ConstructorDeclaration) {
         if (other) {
@@ -71,4 +67,14 @@
       }
     }
   }
+
+  @override
+  void visitClassDeclaration(ClassDeclaration node) {
+    check(node.members);
+  }
+
+  @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    check(node.members);
+  }
 }
diff --git a/lib/src/rules/sort_unnamed_constructors_first.dart b/lib/src/rules/sort_unnamed_constructors_first.dart
index 54102e0..079ac13 100644
--- a/lib/src/rules/sort_unnamed_constructors_first.dart
+++ b/lib/src/rules/sort_unnamed_constructors_first.dart
@@ -10,18 +10,8 @@
 const _desc = r'Sort unnamed constructor declarations first.';
 
 const _details = r'''
-
 **DO** sort unnamed constructor declarations first, before named ones.
 
-**GOOD:**
-```dart
-abstract class CancelableFuture<T> implements Future<T>  {
-  factory CancelableFuture(computation()) => ...
-  factory CancelableFuture.delayed(Duration duration, [computation()]) => ...
-  ...
-}
-```
-
 **BAD:**
 ```dart
 class _PriorityItem {
@@ -31,9 +21,18 @@
 }
 ```
 
+**GOOD:**
+```dart
+abstract class CancelableFuture<T> implements Future<T>  {
+  factory CancelableFuture(computation()) => ...
+  factory CancelableFuture.delayed(Duration duration, [computation()]) => ...
+  ...
+}
+```
+
 ''';
 
-class SortUnnamedConstructorsFirst extends LintRule implements NodeLintRule {
+class SortUnnamedConstructorsFirst extends LintRule {
   SortUnnamedConstructorsFirst()
       : super(
             name: 'sort_unnamed_constructors_first',
@@ -46,6 +45,7 @@
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
     registry.addClassDeclaration(this, visitor);
+    registry.addEnumDeclaration(this, visitor);
   }
 }
 
@@ -54,13 +54,9 @@
 
   _Visitor(this.rule);
 
-  @override
-  void visitClassDeclaration(ClassDeclaration node) {
-    // Sort members by offset.
-    var members = node.members.toList()
-      ..sort((ClassMember m1, ClassMember m2) => m1.offset - m2.offset);
-
+  void check(NodeList<ClassMember> members) {
     var seenConstructor = false;
+    // Members are sorted by source position in the AST.
     for (var member in members) {
       if (member is ConstructorDeclaration) {
         if (member.name == null) {
@@ -73,4 +69,14 @@
       }
     }
   }
+
+  @override
+  void visitClassDeclaration(ClassDeclaration node) {
+    check(node.members);
+  }
+
+  @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    check(node.members);
+  }
 }
diff --git a/lib/src/rules/super_goes_last.dart b/lib/src/rules/super_goes_last.dart
index aaf9f2a..08c8e29 100644
--- a/lib/src/rules/super_goes_last.dart
+++ b/lib/src/rules/super_goes_last.dart
@@ -11,7 +11,6 @@
     r'Place the `super` call last in a constructor initialization list.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **DO** place the `super` call last in a constructor initialization list.
@@ -30,13 +29,6 @@
 reinforces when the superclass's constructor body is run, and may help
 performance.
 
-**GOOD:**
-```dart
-View(Style style, List children)
-    : _children = children,
-      super(style) {
-```
-
 **BAD:**
 ```dart
 View(Style style, List children)
@@ -44,6 +36,13 @@
       _children = children {
 ```
 
+**GOOD:**
+```dart
+View(Style style, List children)
+    : _children = children,
+      super(style) {
+```
+
 **DEPRECATED:** In Dart 2, it is a compile-time error if a superinitializer
 appears in an initializer list at any other position than at the end so this
 rule is made redundant by the Dart analyzer's basic checks and is no longer
@@ -52,7 +51,7 @@
 The rule will be removed in a future Linter release.
 ''';
 
-class SuperGoesLast extends LintRule implements NodeLintRule {
+class SuperGoesLast extends LintRule {
   SuperGoesLast()
       : super(
             name: 'super_goes_last',
diff --git a/lib/src/rules/test_types_in_equals.dart b/lib/src/rules/test_types_in_equals.dart
index b93a1b9..92377db 100644
--- a/lib/src/rules/test_types_in_equals.dart
+++ b/lib/src/rules/test_types_in_equals.dart
@@ -10,12 +10,35 @@
 const _desc = r'Test type arguments in operator ==(Object other).';
 
 const _details = r'''
-
 **DO** test type arguments in operator ==(Object other).
 
 Not testing types might result in null pointer exceptions which will be
 unexpected for consumers of your class.
 
+**BAD:**
+```dart
+class Field {
+}
+
+class Bad {
+  final Field someField;
+
+  Bad(this.someField);
+
+  @override
+  bool operator ==(Object other) {
+    Bad otherBad = other as Bad; // LINT
+    bool areEqual = otherBad != null && otherBad.someField == someField;
+    return areEqual;
+  }
+
+  @override
+  int get hashCode {
+    return someField.hashCode;
+  }
+}
+```
+
 **GOOD:**
 ```dart
 class Field {
@@ -42,33 +65,13 @@
 }
 ```
 
-**BAD:**
-```dart
-class Field {
-}
-
-class Bad {
-  final Field someField;
-
-  Bad(this.someField);
-
-  @override
-  bool operator ==(Object other) {
-    Bad otherBad = other as Bad; // LINT
-    bool areEqual = otherBad != null && otherBad.someField == someField;
-    return areEqual;
-  }
-
-  @override
-  int get hashCode {
-    return someField.hashCode;
-  }
-}
-```
-
 ''';
 
-class TestTypesInEquals extends LintRule implements NodeLintRule {
+class TestTypesInEquals extends LintRule {
+  static const LintCode code = LintCode(
+      'test_types_in_equals', "Missing type test for '{0}' in '=='.",
+      correctionMessage: "Try testing the type of '{0}'.");
+
   TestTypesInEquals()
       : super(
             name: 'test_types_in_equals',
@@ -77,6 +80,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -92,22 +98,36 @@
   @override
   void visitAsExpression(AsExpression node) {
     var declaration = node.thisOrAncestorOfType<MethodDeclaration>();
-    if (!_isEqualsOverride(declaration) ||
-        node.expression is! SimpleIdentifier) {
+    var expression = node.expression;
+    if (!_isEqualsOverride(declaration) || expression is! SimpleIdentifier) {
       return;
     }
 
-    var identifier = node.expression as SimpleIdentifier;
     var parameters = declaration?.parameters;
     var parameterName = parameters?.parameterElements.first?.name;
-    if (identifier.name == parameterName) {
-      rule.reportLint(node);
+    if (expression.name == parameterName) {
+      var typeName = _getTypeName(declaration!);
+      rule.reportLint(node, arguments: [typeName]);
     }
   }
 
+  String _getTypeName(MethodDeclaration method) {
+    var parent = method.parent;
+    if (parent is ClassOrAugmentationDeclaration) {
+      return parent.name.lexeme;
+    } else if (parent is EnumDeclaration) {
+      return parent.name.lexeme;
+    } else if (parent is MixinOrAugmentationDeclaration) {
+      return parent.name.lexeme;
+    } else if (parent is ExtensionDeclaration) {
+      return parent.extendedType.toSource();
+    }
+    return 'unknown';
+  }
+
   bool _isEqualsOverride(MethodDeclaration? declaration) =>
       declaration != null &&
       declaration.isOperator &&
-      declaration.name.name == '==' &&
+      declaration.name.lexeme == '==' &&
       declaration.parameters?.parameterElements.length == 1;
 }
diff --git a/lib/src/rules/throw_in_finally.dart b/lib/src/rules/throw_in_finally.dart
index 8c8c9c5..cbce3c7 100755
--- a/lib/src/rules/throw_in_finally.dart
+++ b/lib/src/rules/throw_in_finally.dart
@@ -11,27 +11,11 @@
 const _desc = r'Avoid `throw` in finally block.';
 
 const _details = r'''
-
 **AVOID** throwing exceptions in finally blocks.
 
 Throwing exceptions in finally blocks will inevitably cause unexpected behavior
 that is hard to debug.
 
-**GOOD:**
-```dart
-class Ok {
-  double compliantMethod() {
-    var i = 5;
-    try {
-      i = 1 / 0;
-    } catch (e) {
-      print(e); // OK
-    }
-    return i;
-  }
-}
-```
-
 **BAD:**
 ```dart
 class BadThrow {
@@ -47,9 +31,28 @@
 }
 ```
 
+**GOOD:**
+```dart
+class Ok {
+  double compliantMethod() {
+    var i = 5;
+    try {
+      i = 1 / 0;
+    } catch (e) {
+      print(e); // OK
+    }
+    return i;
+  }
+}
+```
+
 ''';
 
-class ThrowInFinally extends LintRule implements NodeLintRule {
+class ThrowInFinally extends LintRule {
+  static const LintCode code = LintCode(
+      'throw_in_finally', "Use of '{0}' in 'finally' block.",
+      correctionMessage: "Try moving the '{0}' outside the 'finally' block.");
+
   ThrowInFinally()
       : super(
             name: 'throw_in_finally',
@@ -58,6 +61,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -74,6 +80,6 @@
 
   @override
   void visitThrowExpression(ThrowExpression node) {
-    reportIfFinallyAncestorExists(node);
+    reportIfFinallyAncestorExists(node, kind: 'throw');
   }
 }
diff --git a/lib/src/rules/tighten_type_of_initializing_formals.dart b/lib/src/rules/tighten_type_of_initializing_formals.dart
index 40ac426..aa4cba1 100644
--- a/lib/src/rules/tighten_type_of_initializing_formals.dart
+++ b/lib/src/rules/tighten_type_of_initializing_formals.dart
@@ -13,9 +13,9 @@
 const _desc = r'Tighten type of initializing formal.';
 
 const _details = r'''
-
-Tighten type of initializing formal if a non-null assert exists. This allows the
-type system to catch problems rather than have them only be caught at run-time.
+Tighten the type of an initializing formal if a non-null assert exists. This
+allows the type system to catch problems rather than have them only be caught at
+run-time.
 
 **BAD:**
 ```dart
@@ -29,16 +29,23 @@
 **GOOD:**
 ```dart
 class A {
-  A.c1(String this.p) : assert(p != null);
+  A.c1(String this.p);
   A.c2(this.p);
   final String? p;
 }
-```
 
+class B {
+  String? b;
+  B(this.b);
+}
+
+class C extends B {
+  B(String super.b);
+}
+```
 ''';
 
-class TightenTypeOfInitializingFormals extends LintRule
-    implements NodeLintRule {
+class TightenTypeOfInitializingFormals extends LintRule {
   TightenTypeOfInitializingFormals()
       : super(
           name: 'tighten_type_of_initializing_formals',
@@ -60,34 +67,48 @@
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  _Visitor(this.rule, this.context);
-
   final LintRule rule;
+
   final LinterContext context;
+  _Visitor(this.rule, this.context);
 
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
-    node.initializers
-        .whereType<AssertInitializer>()
-        .map((e) => e.condition)
-        .whereType<BinaryExpression>()
-        .where((e) => e.operator.type == TokenType.BANG_EQ)
-        .map((e) => e.rightOperand is NullLiteral
-            ? e.leftOperand
-            : e.leftOperand is NullLiteral
-                ? e.rightOperand
-                : null)
-        .whereType<Identifier>()
-        .where((e) {
-          var staticType = e.staticType;
-          return staticType != null &&
-              context.typeSystem.isNullable(staticType);
-        })
-        .map((e) => e.staticElement)
-        .whereType<FieldFormalParameterElement>()
-        .forEach((e) {
-          rule.reportLint(node.parameters.parameters
-              .firstWhere((p) => p.declaredElement == e));
-        });
+    for (var initializer in node.initializers) {
+      if (initializer is! AssertInitializer) continue;
+
+      var condition = initializer.condition;
+      if (condition is! BinaryExpression) continue;
+
+      if (condition.operator.type == TokenType.BANG_EQ) {
+        if (condition.rightOperand is NullLiteral) {
+          var leftOperand = condition.leftOperand;
+          if (leftOperand is Identifier) {
+            var staticType = leftOperand.staticType;
+            if (staticType != null &&
+                context.typeSystem.isNullable(staticType)) {
+              _check(leftOperand.staticElement, node);
+            }
+          }
+        } else if (condition.leftOperand is NullLiteral) {
+          var rightOperand = condition.rightOperand;
+          if (rightOperand is Identifier) {
+            var staticType = rightOperand.staticType;
+            if (staticType != null &&
+                context.typeSystem.isNullable(staticType)) {
+              _check(rightOperand.staticElement, node);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  void _check(Element? element, ConstructorDeclaration node) {
+    if (element is FieldFormalParameterElement ||
+        element is SuperFormalParameterElement) {
+      rule.reportLint(node.parameters.parameters
+          .firstWhere((p) => p.declaredElement == element));
+    }
   }
 }
diff --git a/lib/src/rules/type_annotate_public_apis.dart b/lib/src/rules/type_annotate_public_apis.dart
index 0d6f17c..2208a36 100644
--- a/lib/src/rules/type_annotate_public_apis.dart
+++ b/lib/src/rules/type_annotate_public_apis.dart
@@ -7,13 +7,12 @@
 
 import '../analyzer.dart';
 import '../ast.dart';
-import '../utils.dart';
+import '../util/ascii_utils.dart';
 
 const _desc = r'Type annotate public APIs.';
 
 const _details = r'''
-
-From [effective dart](https://dart.dev/guides/language/effective-dart/design#prefer-type-annotating-public-fields-and-top-level-variables-if-the-type-isnt-obvious):
+From [Effective Dart](https://dart.dev/guides/language/effective-dart/design#do-type-annotate-fields-and-top-level-variables-if-the-type-isnt-obvious):
 
 **PREFER** type annotating public APIs.
 
@@ -49,7 +48,7 @@
 
 ''';
 
-class TypeAnnotatePublicApis extends LintRule implements NodeLintRule {
+class TypeAnnotatePublicApis extends LintRule {
   TypeAnnotatePublicApis()
       : super(
             name: 'type_annotate_public_apis',
@@ -88,7 +87,7 @@
         // scope of another function.
         node.parent is CompilationUnit) {
       if (node.returnType == null && !node.isSetter) {
-        rule.reportLint(node.name);
+        rule.reportLintForToken(node.name);
       } else {
         node.functionExpression.parameters?.accept(v);
       }
@@ -99,7 +98,7 @@
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
     if (!isPrivate(node.name)) {
       if (node.returnType == null) {
-        rule.reportLint(node.name);
+        rule.reportLintForToken(node.name);
       } else {
         node.parameters.accept(v);
       }
@@ -110,7 +109,7 @@
   void visitMethodDeclaration(MethodDeclaration node) {
     if (!isPrivate(node.name)) {
       if (node.returnType == null && !node.isSetter) {
-        rule.reportLint(node.name);
+        rule.reportLintForToken(node.name);
       } else {
         node.parameters?.accept(v);
       }
@@ -140,8 +139,8 @@
   @override
   void visitSimpleFormalParameter(SimpleFormalParameter param) {
     if (param.type == null) {
-      var paramName = param.identifier?.name;
-      if (paramName != null && !isJustUnderscores(paramName)) {
+      var paramName = param.name?.lexeme;
+      if (paramName != null && !paramName.isJustUnderscores) {
         rule.reportLint(param);
       }
     }
@@ -152,7 +151,7 @@
     if (!isPrivate(node.name) &&
         !node.isConst &&
         !(node.isFinal && hasInferredType(node))) {
-      rule.reportLint(node.name);
+      rule.reportLintForToken(node.name);
     }
   }
 }
diff --git a/lib/src/rules/type_init_formals.dart b/lib/src/rules/type_init_formals.dart
index 7542493..4965391 100644
--- a/lib/src/rules/type_init_formals.dart
+++ b/lib/src/rules/type_init_formals.dart
@@ -4,19 +4,33 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
 
 const _desc = "Don't type annotate initializing formals.";
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **DON'T** type annotate initializing formals.
 
 If a constructor parameter is using `this.x` to initialize a field, then the
-type of the parameter is understood to be the same type as the field.
+type of the parameter is understood to be the same type as the field. If a 
+a constructor parameter is using `super.x` to forward to a super constructor,
+then the type of the parameter is understood to be the same as the super
+constructor parameter.
+
+Type annotating an initializing formal with a different type than that of the
+field is OK.
+
+**BAD:**
+```dart
+class Point {
+  int x, y;
+  Point(int this.x, int this.y);
+}
+```
 
 **GOOD:**
 ```dart
@@ -28,15 +42,35 @@
 
 **BAD:**
 ```dart
-class Point {
-  int x, y;
-  Point(int this.x, int this.y);
+class A {
+  int a;
+  A(this.a);
+}
+
+class B extends A {
+  B(int super.a);
+}
+```
+
+**GOOD:**
+```dart
+class A {
+  int a;
+  A(this.a);
+}
+
+class B extends A {
+  B(super.a);
 }
 ```
 
 ''';
 
-class TypeInitFormals extends LintRule implements NodeLintRule {
+class TypeInitFormals extends LintRule {
+  static const LintCode code = LintCode('type_init_formals',
+      "Don't needlessly type annotate initializing formals.",
+      correctionMessage: 'Try removing the type.');
+
   TypeInitFormals()
       : super(
             name: 'type_init_formals',
@@ -45,10 +79,14 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
     registry.addFieldFormalParameter(this, visitor);
+    registry.addSuperFormalParameter(this, visitor);
   }
 }
 
@@ -60,17 +98,31 @@
   @override
   void visitFieldFormalParameter(FieldFormalParameter node) {
     var nodeType = node.type;
-    if (nodeType != null) {
-      var cls = node.thisOrAncestorOfType<ClassDeclaration>()?.declaredElement;
-      if (cls != null) {
-        var field = cls.getField(node.identifier.name);
-        // If no such field exists, the code is invalid; do not report lint.
-        if (field != null) {
-          if (nodeType.type == field.type) {
-            rule.reportLint(nodeType);
-          }
-        }
-      }
+    if (nodeType == null) return;
+
+    var paramElement = node.declaredElement;
+    if (paramElement is! FieldFormalParameterElement) return;
+
+    var field = paramElement.field;
+    // If no such field exists, the code is invalid; do not report lint.
+    if (field != null && nodeType.type == field.type) {
+      rule.reportLint(nodeType);
+    }
+  }
+
+  @override
+  void visitSuperFormalParameter(SuperFormalParameter node) {
+    var nodeType = node.type;
+    if (nodeType == null) return;
+
+    var paramElement = node.declaredElement;
+    if (paramElement is! SuperFormalParameterElement) return;
+
+    var superConstructorParameter = paramElement.superConstructorParameter;
+    if (superConstructorParameter == null) return;
+
+    if (superConstructorParameter.type == nodeType.type) {
+      rule.reportLint(nodeType);
     }
   }
 }
diff --git a/lib/src/rules/unawaited_futures.dart b/lib/src/rules/unawaited_futures.dart
index caa1671..51da0b2 100644
--- a/lib/src/rules/unawaited_futures.dart
+++ b/lib/src/rules/unawaited_futures.dart
@@ -7,22 +7,28 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'`Future` results in `async` function bodies must be '
-    '`await`ed or marked `unawaited` using `package:pedantic`.';
+    '`await`ed or marked `unawaited` using `dart:async`.';
 
 const _details = r'''
-
 **DO** await functions that return a `Future` inside of an async function body.
 
 It's easy to forget await in async methods as naming conventions usually don't
 tell us if a method is sync or async (except for some in `dart:io`).
 
 When you really _do_ want to start a fire-and-forget `Future`, the recommended
-way is to use `unawaited` from `package:pedantic`. The `// ignore` and
+way is to use `unawaited` from `dart:async`. The `// ignore` and
 `// ignore_for_file` comments also work.
 
+**BAD:**
+```dart
+void main() async {
+  doSomething(); // Likely a bug.
+}
+```
+
 **GOOD:**
 ```dart
 Future doSomething() => ...;
@@ -34,16 +40,9 @@
 }
 ```
 
-**BAD:**
-```dart
-void main() async {
-  doSomething(); // Likely a bug.
-}
-```
-
 ''';
 
-class UnawaitedFutures extends LintRule implements NodeLintRule {
+class UnawaitedFutures extends LintRule {
   UnawaitedFutures()
       : super(
             name: 'unawaited_futures',
@@ -57,6 +56,7 @@
     var visitor = _Visitor(this);
     registry.addExpressionStatement(this, visitor);
     registry.addCascadeExpression(this, visitor);
+    registry.addInterpolationExpression(this, visitor);
   }
 }
 
@@ -67,12 +67,9 @@
 
   @override
   void visitCascadeExpression(CascadeExpression node) {
-    for (var expr in node.cascadeSections) {
-      if ((expr.staticType?.isDartAsyncFuture ?? false) &&
-          _isEnclosedInAsyncFunctionBody(expr) &&
-          expr is! AssignmentExpression) {
-        rule.reportLint(expr);
-      }
+    var sections = node.cascadeSections;
+    for (var i = 0; i < sections.length; i++) {
+      _visit(sections[i]);
     }
   }
 
@@ -85,7 +82,7 @@
     if (type == null) {
       return;
     }
-    if (DartTypeUtilities.implementsInterface(type, 'Future', 'dart.async')) {
+    if (type.implementsInterface('Future', 'dart.async')) {
       // Ignore a couple of special known cases.
       if (_isFutureDelayedInstanceCreationWithComputation(expr) ||
           _isMapPutIfAbsentInvocation(expr)) {
@@ -100,6 +97,11 @@
     }
   }
 
+  @override
+  void visitInterpolationExpression(InterpolationExpression node) {
+    _visit(node.expression);
+  }
+
   bool _isEnclosedInAsyncFunctionBody(AstNode node) {
     var enclosingFunctionBody = node.thisOrAncestorOfType<FunctionBody>();
     return enclosingFunctionBody?.isAsynchronous ?? false;
@@ -121,4 +123,12 @@
       expr is MethodInvocation &&
       expr.methodName.name == 'putIfAbsent' &&
       _isMapClass(expr.methodName.staticElement?.enclosingElement);
+
+  void _visit(Expression expr) {
+    if ((expr.staticType?.isDartAsyncFuture ?? false) &&
+        _isEnclosedInAsyncFunctionBody(expr) &&
+        expr is! AssignmentExpression) {
+      rule.reportLint(expr);
+    }
+  }
 }
diff --git a/lib/src/rules/unnecessary_await_in_return.dart b/lib/src/rules/unnecessary_await_in_return.dart
index 27c8c75..a2dfc04 100644
--- a/lib/src/rules/unnecessary_await_in_return.dart
+++ b/lib/src/rules/unnecessary_await_in_return.dart
@@ -11,7 +11,6 @@
 const _desc = r'Unnecessary await keyword in return.';
 
 const _details = r'''
-
 Avoid returning an awaited expression when the expression type is assignable to
 the function's return type.
 
@@ -36,7 +35,7 @@
 
 ''';
 
-class UnnecessaryAwaitInReturn extends LintRule implements NodeLintRule {
+class UnnecessaryAwaitInReturn extends LintRule {
   UnnecessaryAwaitInReturn()
       : super(
             name: 'unnecessary_await_in_return',
diff --git a/lib/src/rules/unnecessary_brace_in_string_interps.dart b/lib/src/rules/unnecessary_brace_in_string_interps.dart
index 9c96644..7b3cd91 100644
--- a/lib/src/rules/unnecessary_brace_in_string_interps.dart
+++ b/lib/src/rules/unnecessary_brace_in_string_interps.dart
@@ -11,30 +11,33 @@
 const _desc = r'Avoid using braces in interpolation when not needed.';
 
 const _details = r'''
-
 **AVOID** using braces in interpolation when not needed.
 
 If you're just interpolating a simple identifier, and it's not immediately
 followed by more alphanumeric text, the `{}` can and should be omitted.
 
-**GOOD:**
-```dart
-print("Hi, $name!");
-```
-
 **BAD:**
 ```dart
 print("Hi, ${name}!");
 ```
 
+**GOOD:**
+```dart
+print("Hi, $name!");
+```
+
 ''';
 
-final RegExp identifierPart = RegExp(r'^[a-zA-Z0-9_]');
+final RegExp identifierPart = RegExp('[a-zA-Z0-9_]');
 
 bool isIdentifierPart(Token? token) =>
     token is StringToken && token.lexeme.startsWith(identifierPart);
 
-class UnnecessaryBraceInStringInterps extends LintRule implements NodeLintRule {
+class UnnecessaryBraceInStringInterps extends LintRule {
+  static const LintCode code = LintCode('unnecessary_brace_in_string_interps',
+      'Unnecessary braces in a string interpolation.',
+      correctionMessage: 'Try removing the braces.');
+
   UnnecessaryBraceInStringInterps()
       : super(
             name: 'unnecessary_brace_in_string_interps',
@@ -43,6 +46,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -62,13 +68,19 @@
       var exp = expression.expression;
       if (exp is SimpleIdentifier) {
         var identifier = exp;
-        var bracket = expression.rightBracket;
-        if (bracket != null &&
-            !isIdentifierPart(bracket.next) &&
-            !identifier.name.contains('\$')) {
-          rule.reportLint(expression);
+        if (!identifier.name.contains('\$')) {
+          _check(expression);
         }
+      } else if (exp is ThisExpression) {
+        _check(expression);
       }
     }
   }
+
+  void _check(InterpolationExpression expression) {
+    var bracket = expression.rightBracket;
+    if (bracket != null && !isIdentifierPart(bracket.next)) {
+      rule.reportLint(expression);
+    }
+  }
 }
diff --git a/lib/src/rules/unnecessary_const.dart b/lib/src/rules/unnecessary_const.dart
index 6c4631a..00078f0 100644
--- a/lib/src/rules/unnecessary_const.dart
+++ b/lib/src/rules/unnecessary_const.dart
@@ -11,7 +11,6 @@
 const _desc = r'Avoid const keyword.';
 
 const _details = r'''
-
 **AVOID** repeating const keyword in a const context.
 
 **BAD:**
@@ -34,7 +33,11 @@
 
 ''';
 
-class UnnecessaryConst extends LintRule implements NodeLintRule {
+class UnnecessaryConst extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_const', "Unnecessary 'const' keyword.",
+      correctionMessage: 'Try removing the keyword.');
+
   UnnecessaryConst()
       : super(
             name: 'unnecessary_const',
@@ -43,11 +46,15 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
     registry.addInstanceCreationExpression(this, visitor);
     registry.addListLiteral(this, visitor);
+    registry.addRecordLiteral(this, visitor);
     registry.addSetOrMapLiteral(this, visitor);
   }
 }
@@ -69,6 +76,15 @@
   void visitListLiteral(ListLiteral node) => _visitTypedLiteral(node);
 
   @override
+  void visitRecordLiteral(RecordLiteral node) {
+    if (node.constKeyword == null) return;
+
+    if (node.inConstantContext) {
+      rule.reportLint(node);
+    }
+  }
+
+  @override
   void visitSetOrMapLiteral(SetOrMapLiteral node) {
     _visitTypedLiteral(node);
   }
diff --git a/lib/src/rules/unnecessary_constructor_name.dart b/lib/src/rules/unnecessary_constructor_name.dart
new file mode 100644
index 0000000..70b9785
--- /dev/null
+++ b/lib/src/rules/unnecessary_constructor_name.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Unnecessary `.new` constructor name.';
+
+const _details = r'''
+**PREFER** using the default unnamed Constructor over `.new`.
+
+Given a class `C`, the named unnamed constructor `C.new` refers to the same
+constructor as the unnamed `C`. As such it adds nothing but visual noise to
+invocations and should be avoided (unless being used to identify a constructor
+tear-off).
+
+**BAD:**
+```dart
+class A {
+  A.new(); // LINT
+}
+
+var a = A.new(); // LINT
+```
+
+**GOOD:**
+```dart
+class A {
+  A.ok();
+}
+
+var a = A();
+var aa = A.ok();
+var makeA = A.new;
+```
+''';
+
+class UnnecessaryConstructorName extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_constructor_name', "Unnecessary '.new' constructor name.",
+      correctionMessage: "Try removing the '.new'.");
+
+  UnnecessaryConstructorName()
+      : super(
+            name: 'unnecessary_constructor_name',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addConstructorDeclaration(this, visitor);
+    registry.addInstanceCreationExpression(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitConstructorDeclaration(ConstructorDeclaration node) {
+    _check(node.name);
+  }
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    _check(node.constructorName.name?.token);
+  }
+
+  void _check(Token? name) {
+    if (name?.lexeme == 'new') {
+      rule.reportLintForToken(name);
+    }
+  }
+}
diff --git a/lib/src/rules/unnecessary_final.dart b/lib/src/rules/unnecessary_final.dart
index 1e88b93..0ab6059 100644
--- a/lib/src/rules/unnecessary_final.dart
+++ b/lib/src/rules/unnecessary_final.dart
@@ -10,9 +10,14 @@
 const _desc = "Don't use `final` for local variables.";
 
 const _details = r'''
-**DON'T** use `final` for local variables.
+Use `var`, not `final`, when declaring local variables.
 
-`var` is shorter, and `final` does not change the meaning of the code.
+Per [Effective Dart](https://dart.dev/guides/language/effective-dart/usage#do-follow-a-consistent-rule-for-var-and-final-on-local-variables),
+there are two styles in wide use. This rule enforces the `var` style.
+For the alternative style that prefers `final`, enable `prefer_final_locals`
+and `prefer_final_in_for_each` instead.
+
+For fields, `final` is always recommended; see the rule `prefer_final_fields`.
 
 **BAD:**
 ```dart
@@ -35,7 +40,7 @@
 ```
 ''';
 
-class UnnecessaryFinal extends LintRule implements NodeLintRule {
+class UnnecessaryFinal extends LintRule {
   UnnecessaryFinal()
       : super(
             name: 'unnecessary_final',
diff --git a/lib/src/rules/unnecessary_getters.dart b/lib/src/rules/unnecessary_getters.dart
index fa6b0c1..717e100 100644
--- a/lib/src/rules/unnecessary_getters.dart
+++ b/lib/src/rules/unnecessary_getters.dart
@@ -13,7 +13,6 @@
     r'getter.';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **PREFER** using a public final field instead of a private field with a public
@@ -40,7 +39,7 @@
 
 ''';
 
-class UnnecessaryGetters extends LintRule implements NodeLintRule {
+class UnnecessaryGetters extends LintRule {
   UnnecessaryGetters()
       : super(
             name: 'unnecessary_getters',
@@ -73,9 +72,9 @@
     for (var member in members) {
       var method = member as MethodDeclaration;
       if (method.isGetter) {
-        getters[method.name.toString()] = method;
+        getters[method.name.lexeme] = method;
       } else if (method.isSetter) {
-        setters[method.name.toString()] = method;
+        setters[method.name.lexeme] = method;
       }
     }
 
@@ -86,7 +85,7 @@
 
   void _visitGetter(MethodDeclaration? getter) {
     if (getter != null && isSimpleGetter(getter)) {
-      rule.reportLint(getter.name);
+      rule.reportLintForToken(getter.name);
     }
   }
 }
diff --git a/lib/src/rules/unnecessary_getters_setters.dart b/lib/src/rules/unnecessary_getters_setters.dart
index 2639f00..b923c82 100644
--- a/lib/src/rules/unnecessary_getters_setters.dart
+++ b/lib/src/rules/unnecessary_getters_setters.dart
@@ -12,7 +12,6 @@
     r'Avoid wrapping fields in getters and setters just to be "safe".';
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **AVOID** wrapping fields in getters and setters just to be "safe".
@@ -28,16 +27,7 @@
 indistinguishable.  You can expose a field in a class and later wrap it in a
 getter and setter without having to touch any code that uses that field.
 
-**GOOD:**
-
-```dart
-class Box {
-  var contents;
-}
-```
-
 **BAD:**
-
 ```dart
 class Box {
   var _contents;
@@ -48,9 +38,21 @@
 }
 ```
 
+**GOOD:**
+```dart
+class Box {
+  var contents;
+}
+```
+
 ''';
 
-class UnnecessaryGettersSetters extends LintRule implements NodeLintRule {
+class UnnecessaryGettersSetters extends LintRule {
+  static const LintCode code = LintCode('unnecessary_getters_setters',
+      'Unnecessary use of getter and setter to wrap a field.',
+      correctionMessage:
+          'Try removing the getter and setter and renaming the field.');
+
   UnnecessaryGettersSetters()
       : super(
             name: 'unnecessary_getters_setters',
@@ -59,6 +61,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -73,18 +78,20 @@
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
+    _check(node.members);
+  }
+
+  void _check(NodeList<ClassMember> members) {
     var getters = <String, MethodDeclaration>{};
     var setters = <String, MethodDeclaration>{};
 
     // Build getter/setter maps
-    var members = node.members.where(isMethod);
-
-    for (var member in members) {
-      var method = member as MethodDeclaration;
+    for (var method in members.whereType<MethodDeclaration>()) {
+      var methodName = method.name.lexeme;
       if (method.isGetter) {
-        getters[method.name.toString()] = method;
+        getters[methodName] = method;
       } else if (method.isSetter) {
-        setters[method.name.toString()] = method;
+        setters[methodName] = method;
       }
     }
 
@@ -103,9 +110,8 @@
         isSimpleGetter(getter) &&
         getterElement.metadata.isEmpty &&
         setterElement.metadata.isEmpty) {
-      rule
-        ..reportLint(getter.name)
-        ..reportLint(setter.name);
+      // Just flag the getter (https://github.com/dart-lang/linter/issues/2851)
+      rule.reportLintForToken(getter.name);
     }
   }
 }
diff --git a/lib/src/rules/unnecessary_lambdas.dart b/lib/src/rules/unnecessary_lambdas.dart
index 53aeba9..274319a 100644
--- a/lib/src/rules/unnecessary_lambdas.dart
+++ b/lib/src/rules/unnecessary_lambdas.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -13,7 +14,6 @@
 const _desc = r"Don't create a lambda when a tear-off will do.";
 
 const _details = r'''
-
 **DON'T** create a lambda when a tear-off will do.
 
 **BAD:**
@@ -42,11 +42,9 @@
             _containsNullAwareInvocationInChain(node.target)));
 
 Iterable<Element?> _extractElementsOfSimpleIdentifiers(AstNode node) =>
-    DartTypeUtilities.traverseNodesInDFS(node)
-        .whereType<SimpleIdentifier>()
-        .map((e) => e.staticElement);
+    _IdentifierVisitor().extractElements(node);
 
-class UnnecessaryLambdas extends LintRule implements NodeLintRule {
+class UnnecessaryLambdas extends LintRule {
   UnnecessaryLambdas()
       : super(
             name: 'unnecessary_lambdas',
@@ -110,11 +108,31 @@
   }
 }
 
+class _IdentifierVisitor extends RecursiveAstVisitor {
+  final _elements = <Element?>[];
+
+  _IdentifierVisitor();
+
+  List<Element?> extractElements(AstNode node) {
+    node.accept(this);
+    return _elements;
+  }
+
+  @override
+  visitSimpleIdentifier(SimpleIdentifier node) {
+    _elements.add(node.staticElement);
+    super.visitSimpleIdentifier(node);
+  }
+}
+
 class _Visitor extends SimpleAstVisitor<void> {
+  final bool constructorTearOffsEnabled;
   final LintRule rule;
   final LinterContext context;
 
-  _Visitor(this.rule, this.context);
+  _Visitor(this.rule, this.context)
+      : constructorTearOffsEnabled =
+            context.isEnabled(Feature.constructor_tearoffs);
 
   @override
   void visitFunctionExpression(FunctionExpression node) {
@@ -124,7 +142,6 @@
     var body = node.body;
     if (body is BlockFunctionBody && body.block.statements.length == 1) {
       var statement = body.block.statements.single;
-
       if (statement is ExpressionStatement &&
           statement.expression is InvocationExpression) {
         _visitInvocationExpression(
@@ -137,18 +154,45 @@
         }
       }
     } else if (body is ExpressionFunctionBody) {
-      if (body.expression is InvocationExpression) {
-        _visitInvocationExpression(
-            body.expression as InvocationExpression, node);
+      var expression = body.expression;
+      if (expression is InvocationExpression) {
+        _visitInvocationExpression(expression, node);
+      } else if (constructorTearOffsEnabled &&
+          expression is InstanceCreationExpression) {
+        _visitInstanceCreation(expression, node);
       }
     }
   }
 
+  void _visitInstanceCreation(
+      InstanceCreationExpression expression, FunctionExpression node) {
+    if (expression.isConst) return;
+
+    var arguments = expression.argumentList.arguments;
+    var parameters = node.parameters?.parameters ?? <FormalParameter>[];
+    if (parameters.length != arguments.length) return;
+
+    bool matches(Expression argument, FormalParameter parameter) {
+      if (argument is SimpleIdentifier) {
+        return argument.name == parameter.name?.lexeme;
+      }
+      return false;
+    }
+
+    for (var i = 0; i < arguments.length; ++i) {
+      if (!matches(arguments[i], parameters[i])) {
+        return;
+      }
+    }
+
+    rule.reportLint(node);
+  }
+
   void _visitInvocationExpression(
       InvocationExpression node, FunctionExpression nodeToLint) {
     var nodeToLintParams = nodeToLint.parameters?.parameters;
     if (nodeToLintParams == null ||
-        !DartTypeUtilities.matchesArgumentsWithParameters(
+        !argumentsMatchParameters(
             node.argumentList.arguments, nodeToLintParams)) {
       return;
     }
@@ -177,8 +221,7 @@
       if (target is SimpleIdentifier) {
         var element = target.staticElement;
         if (element is PrefixElement) {
-          var imports = element.enclosingElement.getImportsWithPrefix(element);
-          for (var import in imports) {
+          for (var import in element.imports) {
             if (import.isDeferred) {
               return;
             }
@@ -212,3 +255,7 @@
     }
   }
 }
+
+extension LibraryImportElementExtension on LibraryImportElement {
+  bool get isDeferred => prefix is DeferredImportElementPrefix;
+}
diff --git a/lib/src/rules/unnecessary_late.dart b/lib/src/rules/unnecessary_late.dart
new file mode 100644
index 0000000..712238a
--- /dev/null
+++ b/lib/src/rules/unnecessary_late.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc = r"Don't specify the `late` modifier when it is not needed.";
+
+const _details = r'''
+**DO** not specify the `late` modifier for top-level and static variables
+when the declaration contains an initializer. 
+
+Top-level and static variables with initializers are already evaluated lazily
+as if they are marked `late`.
+
+**BAD:**
+```dart
+late String badTopLevel = '';
+```
+
+**GOOD:**
+```dart
+String goodTopLevel = '';
+```
+
+**BAD:**
+```dart
+class BadExample {
+  static late String badStatic = '';
+}
+```
+
+**GOOD:**
+```dart
+class GoodExample {
+  late String goodStatic;
+}
+```
+''';
+
+class UnnecessaryLate extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_late', "Unnecessary 'late' modifier.",
+      correctionMessage: "Try removing the 'late'.");
+
+  UnnecessaryLate()
+      : super(
+            name: 'unnecessary_late',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addFieldDeclaration(this, visitor);
+    registry.addTopLevelVariableDeclaration(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitFieldDeclaration(FieldDeclaration node) {
+    if (node.isStatic) {
+      _visitVariableDeclarations(node.fields);
+    }
+  }
+
+  @override
+  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+    _visitVariableDeclarations(node.variables);
+  }
+
+  void _visitVariableDeclarations(VariableDeclarationList node) {
+    if (node.lateKeyword == null) {
+      return;
+    }
+
+    for (var variable in node.variables) {
+      if (variable.initializer != null) {
+        rule.reportLintForToken(variable.name);
+      }
+    }
+  }
+}
diff --git a/lib/src/rules/unnecessary_library_directive.dart b/lib/src/rules/unnecessary_library_directive.dart
new file mode 100644
index 0000000..34eed3c
--- /dev/null
+++ b/lib/src/rules/unnecessary_library_directive.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _name = 'unnecessary_library_directive';
+
+const _desc =
+    'Avoid library directives unless they have documentation comments or '
+    'annotations.';
+
+const _details = r'''
+**DO** use library directives if you want to document a library and/or annotate 
+a library.
+
+**BAD:**
+```dart
+library;
+```
+
+**GOOD:**
+```dart
+/// This library does important things
+library;
+```
+
+```dart
+@TestOn('js')
+library;
+```
+
+NOTE: Due to limitations with this lint, libraries with parts will not be
+flagged for unnecessary library directives.
+''';
+
+class UnnecessaryLibraryDirective extends LintRule {
+  static const LintCode code = LintCode(
+    _name,
+    'Library directives without comments or annotations should be avoided.',
+    correctionMessage: 'Try deleting the library directive.',
+  );
+
+  UnnecessaryLibraryDirective()
+      : super(
+          name: _name,
+          description: _desc,
+          details: _details,
+          group: Group.style,
+        );
+
+  @override
+  LintCode get lintCode => code;
+
+  @override
+  void registerNodeProcessors(
+    NodeLintRegistry registry,
+    LinterContext context,
+  ) {
+    var visitor = _Visitor(this);
+    registry.addLibraryDirective(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitLibraryDirective(LibraryDirective node) {
+    var parent = node.parent! as CompilationUnit;
+    if (parent.directives.any((element) => element is PartDirective)) {
+      // Parts may still use library names. No be safe, we don't lint those
+      // libraries – even though using library names itself is discouraged.
+      return;
+    }
+
+    if (node.sortedCommentAndAnnotations.isEmpty) {
+      rule.reportLint(node);
+    }
+  }
+}
diff --git a/lib/src/rules/unnecessary_new.dart b/lib/src/rules/unnecessary_new.dart
index 7fc4f62..23155ca 100644
--- a/lib/src/rules/unnecessary_new.dart
+++ b/lib/src/rules/unnecessary_new.dart
@@ -11,7 +11,6 @@
 const _desc = r'Unnecessary new keyword.';
 
 const _details = r'''
-
 **AVOID** new keyword to create instances.
 
 **BAD:**
@@ -32,7 +31,11 @@
 
 ''';
 
-class UnnecessaryNew extends LintRule implements NodeLintRule {
+class UnnecessaryNew extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_new', "Unnecessary 'new' keyword.",
+      correctionMessage: "Try removing the 'new' keyword.");
+
   UnnecessaryNew()
       : super(
             name: 'unnecessary_new',
@@ -41,6 +44,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
diff --git a/lib/src/rules/unnecessary_null_aware_assignments.dart b/lib/src/rules/unnecessary_null_aware_assignments.dart
index 07da304..6ecfe6f 100644
--- a/lib/src/rules/unnecessary_null_aware_assignments.dart
+++ b/lib/src/rules/unnecessary_null_aware_assignments.dart
@@ -5,34 +5,38 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Avoid null in null-aware assignment.';
 
 const _details = r'''
-
 **AVOID** `null` in null-aware assignment.
 
 Using `null` on the right-hand side of a null-aware assignment effectively makes
 the assignment redundant.
 
-**GOOD:**
-```dart
-var x;
-x ??= 1;
-```
-
 **BAD:**
 ```dart
 var x;
 x ??= null;
 ```
 
+**GOOD:**
+```dart
+var x;
+x ??= 1;
+```
+
 ''';
 
-class UnnecessaryNullAwareAssignments extends LintRule implements NodeLintRule {
+class UnnecessaryNullAwareAssignments extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_null_aware_assignments', "Unnecessary assignment of 'null'.",
+      correctionMessage: 'Try removing the assignment.');
+
   UnnecessaryNullAwareAssignments()
       : super(
             name: 'unnecessary_null_aware_assignments',
@@ -41,6 +45,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -55,8 +62,11 @@
 
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
+    if (node.readElement is PropertyAccessorElement) return;
+    if (node.writeElement is PropertyAccessorElement) return;
+
     if (node.operator.type == TokenType.QUESTION_QUESTION_EQ &&
-        DartTypeUtilities.isNullLiteral(node.rightHandSide)) {
+        node.rightHandSide.isNullLiteral) {
       rule.reportLint(node);
     }
   }
diff --git a/lib/src/rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart b/lib/src/rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart
new file mode 100644
index 0000000..6db5606
--- /dev/null
+++ b/lib/src/rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+
+import '../analyzer.dart';
+
+const _desc =
+    r'Unnecessary null aware operator on extension on a nullable type.';
+
+const _details = r'''
+Avoid null aware operators for members defined in an extension on a nullable type.
+
+**BAD:**
+
+```dart
+extension E on int? {
+  int m() => 1;
+}
+f(int? i) => i?.m();
+```
+
+**GOOD:**
+
+```dart
+extension E on int? {
+  int m() => 1;
+}
+f(int? i) => i.m();
+```
+
+''';
+
+class UnnecessaryNullAwareOperatorOnExtensionOnNullable extends LintRule {
+  UnnecessaryNullAwareOperatorOnExtensionOnNullable()
+      : super(
+          name: 'unnecessary_null_aware_operator_on_extension_on_nullable',
+          description: _desc,
+          details: _details,
+          group: Group.style,
+        );
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this, context);
+    registry.addIndexExpression(this, visitor);
+    registry.addMethodInvocation(this, visitor);
+    registry.addPropertyAccess(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  _Visitor(this.rule, this.context);
+
+  final LintRule rule;
+  final LinterContext context;
+
+  @override
+  void visitIndexExpression(IndexExpression node) {
+    if (node.isNullAware &&
+        _isExtensionOnNullableType(node.inSetterContext()
+            ? (node.thisOrAncestorOfType<AssignmentExpression>())
+                ?.writeElement
+                ?.enclosingElement
+            : node.staticElement?.enclosingElement)) {
+      rule.reportLintForToken(node.question);
+    }
+  }
+
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    if (node.isNullAware &&
+        _isExtensionOnNullableType(
+            node.methodName.staticElement?.enclosingElement)) {
+      rule.reportLintForToken(node.operator);
+    }
+  }
+
+  @override
+  void visitPropertyAccess(PropertyAccess node) {
+    if (node.isNullAware) {
+      var realParent = node.thisOrAncestorMatching(
+          (p) => p != node && p is! ParenthesizedExpression);
+      if (_isExtensionOnNullableType(realParent is AssignmentExpression
+          ? realParent.writeElement?.enclosingElement
+          : node.propertyName.staticElement?.enclosingElement)) {
+        rule.reportLintForToken(node.operator);
+      }
+    }
+  }
+
+  bool _isExtensionOnNullableType(Element? enclosingElement) =>
+      enclosingElement is ExtensionElement &&
+      context.typeSystem.isNullable(enclosingElement.extendedType);
+}
diff --git a/lib/src/rules/unnecessary_null_checks.dart b/lib/src/rules/unnecessary_null_checks.dart
index 1420ae1..bf7cb1a 100644
--- a/lib/src/rules/unnecessary_null_checks.dart
+++ b/lib/src/rules/unnecessary_null_checks.dart
@@ -12,8 +12,7 @@
 const _desc = r'Unnecessary null checks.';
 
 const _details = r'''
-
-Don't apply a null check when a nullable value is accepted.
+**DON'T** apply a null check when a nullable value is accepted.
 
 **BAD:**
 ```dart
@@ -40,6 +39,10 @@
   var realNode =
       node.thisOrAncestorMatching((e) => e.parent is! ParenthesizedExpression);
   var parent = realNode?.parent;
+  var withAwait = parent is AwaitExpression;
+  if (withAwait) {
+    parent = parent.parent;
+  }
 
   // in return value
   if (parent is ReturnStatement || parent is ExpressionFunctionBody) {
@@ -48,7 +51,32 @@
       return null;
     }
     var staticType = parentExpression.staticType;
-    return staticType is FunctionType ? staticType.returnType : null;
+    if (staticType is! FunctionType) {
+      return null;
+    }
+    staticType = staticType.returnType;
+    if (withAwait || parentExpression.body.keyword?.lexeme == 'async') {
+      return staticType.isDartAsyncFuture || staticType.isDartAsyncFutureOr
+          ? (staticType as ParameterizedType?)?.typeArguments.first
+          : null;
+    } else {
+      return staticType;
+    }
+  }
+  // in yield value
+  if (parent is YieldStatement) {
+    var parentExpression = parent.thisOrAncestorOfType<FunctionExpression>();
+    if (parentExpression == null) {
+      return null;
+    }
+    var staticType = parentExpression.staticType;
+    if (staticType is! FunctionType) {
+      return null;
+    }
+    staticType = staticType.returnType;
+    return staticType.isDartCoreIterable || staticType.isDartAsyncStream
+        ? (staticType as ParameterizedType).typeArguments.first
+        : null;
   }
   // assignment
   if (parent is AssignmentExpression &&
@@ -71,6 +99,30 @@
     }
     return parentElement.parameters.first.type;
   }
+  // as member of list
+  if (parent is ListLiteral) {
+    return (parent.staticType as ParameterizedType?)?.typeArguments.first;
+  }
+  // as member of set
+  if (parent is SetOrMapLiteral && parent.isSet) {
+    return (parent.staticType as ParameterizedType?)?.typeArguments.first;
+  }
+  // as member of map
+  if (parent is MapLiteralEntry) {
+    var grandParent = parent.parent;
+    while (true) {
+      if (grandParent is ForElement) {
+        grandParent = grandParent.parent;
+      } else if (grandParent is IfElement) {
+        grandParent = grandParent.parent;
+      } else if (grandParent is SetOrMapLiteral) {
+        var type = grandParent.staticType as InterfaceType?;
+        return type?.typeArguments[parent.key == node ? 0 : 1];
+      } else {
+        return null;
+      }
+    }
+  }
   // as parameter of function
   if (parent is NamedExpression) {
     realNode = parent;
@@ -82,7 +134,7 @@
   return null;
 }
 
-class UnnecessaryNullChecks extends LintRule implements NodeLintRule {
+class UnnecessaryNullChecks extends LintRule {
   UnnecessaryNullChecks()
       : super(
             name: 'unnecessary_null_checks',
diff --git a/lib/src/rules/unnecessary_null_in_if_null_operators.dart b/lib/src/rules/unnecessary_null_in_if_null_operators.dart
index fbf4d0a..c0069c7 100644
--- a/lib/src/rules/unnecessary_null_in_if_null_operators.dart
+++ b/lib/src/rules/unnecessary_null_in_if_null_operators.dart
@@ -7,32 +7,35 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Avoid using `null` in `if null` operators.';
 
 const _details = r'''
-
 **AVOID** using `null` as an operand in `if null` operators.
 
 Using `null` in an `if null` operator is redundant, regardless of which side
 `null` is used on.
 
-**GOOD:**
-```dart
-var x = a ?? 1;
-```
-
 **BAD:**
 ```dart
 var x = a ?? null;
 var y = null ?? 1;
 ```
 
+**GOOD:**
+```dart
+var x = a ?? 1;
+```
+
 ''';
 
-class UnnecessaryNullInIfNullOperators extends LintRule
-    implements NodeLintRule {
+class UnnecessaryNullInIfNullOperators extends LintRule {
+  static const LintCode code = LintCode('unnecessary_null_in_if_null_operators',
+      "Unnecessary use of '??' with 'null'.",
+      correctionMessage:
+          "Try removing the '??' operator and the 'null' operand.");
+
   UnnecessaryNullInIfNullOperators()
       : super(
             name: 'unnecessary_null_in_if_null_operators',
@@ -41,6 +44,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -56,8 +62,7 @@
   @override
   void visitBinaryExpression(BinaryExpression node) {
     if (node.operator.type == TokenType.QUESTION_QUESTION &&
-        (DartTypeUtilities.isNullLiteral(node.rightOperand) ||
-            DartTypeUtilities.isNullLiteral(node.leftOperand))) {
+        (node.rightOperand.isNullLiteral || node.leftOperand.isNullLiteral)) {
       rule.reportLint(node);
     }
   }
diff --git a/lib/src/rules/unnecessary_nullable_for_final_variable_declarations.dart b/lib/src/rules/unnecessary_nullable_for_final_variable_declarations.dart
index 7247bfc..2085995 100644
--- a/lib/src/rules/unnecessary_nullable_for_final_variable_declarations.dart
+++ b/lib/src/rules/unnecessary_nullable_for_final_variable_declarations.dart
@@ -12,7 +12,6 @@
     'with a non-nullable value.';
 
 const _details = r'''
-
 Use a non-nullable type for a final variable initialized with a non-nullable
 value.
 
@@ -28,8 +27,12 @@
 
 ''';
 
-class UnnecessaryNullableForFinalVariableDeclarations extends LintRule
-    implements NodeLintRule {
+class UnnecessaryNullableForFinalVariableDeclarations extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_nullable_for_final_variable_declarations',
+      'Type could be non-nullable.',
+      correctionMessage: 'Try changing the type to be non-nullable.');
+
   UnnecessaryNullableForFinalVariableDeclarations()
       : super(
             name: 'unnecessary_nullable_for_final_variable_declarations',
@@ -39,6 +42,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     if (!context.isEnabled(Feature.non_nullable)) {
@@ -46,7 +52,6 @@
     }
 
     var visitor = _Visitor(this, context);
-    registry.addCompilationUnit(this, visitor);
     registry.addFieldDeclaration(this, visitor);
     registry.addTopLevelVariableDeclaration(this, visitor);
     registry.addVariableDeclarationStatement(this, visitor);
@@ -54,15 +59,15 @@
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  _Visitor(this.rule, this.context);
-
   final LintRule rule;
+
   final LinterContext context;
+  _Visitor(this.rule, this.context);
 
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
     for (var variable in node.fields.variables) {
-      if (Identifier.isPrivateName(variable.name.name) || node.isStatic) {
+      if (Identifier.isPrivateName(variable.name.lexeme) || node.isStatic) {
         _visit(variable);
       }
     }
diff --git a/lib/src/rules/unnecessary_overrides.dart b/lib/src/rules/unnecessary_overrides.dart
index ec532e5..3ddab71 100644
--- a/lib/src/rules/unnecessary_overrides.dart
+++ b/lib/src/rules/unnecessary_overrides.dart
@@ -7,6 +7,7 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
+import '../extensions.dart';
 import '../util/dart_type_utilities.dart';
 
 const _desc =
@@ -14,7 +15,6 @@
     r' parameters.';
 
 const _details = r'''
-
 **DON'T** override a method to do a super method invocation with same parameters.
 
 **BAD:**
@@ -40,16 +40,22 @@
 It's valid to override a member in the following cases:
 
 * if a type (return type or a parameter type) is not the exactly the same as the
-super method,
+  super member,
 * if the `covariant` keyword is added to one of the parameters,
 * if documentation comments are present on the member,
-* if the member has annotations other than `@override`.
+* if the member has annotations other than `@override`,
+* if the member is not annotated with `@protected`, and the super member is.
 
 `noSuchMethod` is a special method and is not checked by this rule.
 
 ''';
 
-class UnnecessaryOverrides extends LintRule implements NodeLintRule {
+class UnnecessaryOverrides extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_overrides', 'Unnecessary override.',
+      correctionMessage:
+          'Try adding behavior in the overriding member or removing the override.');
+
   UnnecessaryOverrides()
       : super(
             name: 'unnecessary_overrides',
@@ -58,6 +64,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -68,7 +77,9 @@
 abstract class _AbstractUnnecessaryOverrideVisitor extends SimpleAstVisitor {
   final LintRule rule;
 
-  ExecutableElement? inheritedMethod;
+  /// If [declaration] is an inherited member of interest, then this is set in
+  /// [visitMethodDeclaration].
+  late ExecutableElement _inheritedMethod;
   late MethodDeclaration declaration;
 
   _AbstractUnnecessaryOverrideVisitor(this.rule);
@@ -99,17 +110,27 @@
 
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    // noSuchMethod is mandatory to proxify
-    if (node.name.name == 'noSuchMethod') return;
+    // 'noSuchMethod' is mandatory to proxify.
+    if (node.name.lexeme == 'noSuchMethod') return;
 
-    // it's ok to override to have better documentation
+    // It's ok to override to have better documentation.
     if (node.documentationComment != null) return;
 
-    inheritedMethod = getInheritedElement(node);
+    var inheritedMethod = getInheritedElement(node);
+    if (inheritedMethod == null) return;
+    _inheritedMethod = inheritedMethod;
     declaration = node;
-    if (inheritedMethod != null && !_addsMetadata() && _haveSameDeclaration()) {
-      node.body.accept(this);
-    }
+
+    // It's ok to override to add annotations.
+    if (_addsMetadata()) return;
+
+    // It's ok to override to change the signature.
+    if (!_haveSameDeclaration()) return;
+
+    // It's ok to override to make a `@protected` method public.
+    if (_makesPublicFromProtected()) return;
+
+    node.body.accept(this);
   }
 
   @override
@@ -119,21 +140,27 @@
 
   @override
   void visitReturnStatement(ReturnStatement node) {
+    if (node.beginToken.precedingComments != null) return;
     node.expression?.accept(this);
   }
 
   @override
   void visitSuperExpression(SuperExpression node) {
-    rule.reportLint(declaration.name);
+    if (node.beginToken.precedingComments != null) return;
+    rule.reportLintForToken(declaration.name);
   }
 
+  /// Returns whether [declaration] is annotated with any metadata (other than
+  /// `@override` or `@Override`).
   bool _addsMetadata() {
     var metadata = declaration.declaredElement?.metadata;
     if (metadata != null) {
       for (var annotation in metadata) {
-        if (!annotation.isOverride) {
-          return true;
-        }
+        if (annotation.isOverride) continue;
+        if (annotation.isProtected && _inheritedMethod.hasProtected) continue;
+
+        // Any other annotation implies a meaningful override.
+        return true;
       }
     }
     return false;
@@ -144,19 +171,15 @@
     if (declaredElement == null) {
       return false;
     }
-    var inheritedMethod = this.inheritedMethod;
-    if (inheritedMethod == null) {
-      return false;
-    }
-    if (declaredElement.returnType != inheritedMethod.returnType) {
+    if (declaredElement.returnType != _inheritedMethod.returnType) {
       return false;
     }
     if (declaredElement.parameters.length !=
-        inheritedMethod.parameters.length) {
+        _inheritedMethod.parameters.length) {
       return false;
     }
-    for (var i = 0; i < inheritedMethod.parameters.length; i++) {
-      var superParam = inheritedMethod.parameters[i];
+    for (var i = 0; i < _inheritedMethod.parameters.length; i++) {
+      var superParam = _inheritedMethod.parameters[i];
       var param = declaredElement.parameters[i];
       if (param.type != superParam.type) return false;
       if (param.name != superParam.name) return false;
@@ -167,9 +190,23 @@
     return true;
   }
 
+  /// Returns true if [_inheritedMethod] is `@protected` and [declaration] is
+  /// not `@protected`, and false otherwise.
+  ///
+  /// This indicates that [_inheritedMethod] may have been overridden in order
+  /// to expand its visibility.
+  bool _makesPublicFromProtected() {
+    var declaredElement = declaration.declaredElement;
+    if (declaredElement == null) return false;
+    if (declaredElement.hasProtected) {
+      return false;
+    }
+    return _inheritedMethod.hasProtected;
+  }
+
   bool _sameKind(ParameterElement first, ParameterElement second) {
-    if (first.isNotOptional) {
-      return second.isNotOptional;
+    if (first.isRequired) {
+      return second.isRequired;
     } else if (first.isOptionalPositional) {
       return second.isOptionalPositional;
     } else if (first.isNamed) {
@@ -181,15 +218,25 @@
 
 class _UnnecessaryGetterOverrideVisitor
     extends _AbstractUnnecessaryOverrideVisitor {
-  _UnnecessaryGetterOverrideVisitor(LintRule rule) : super(rule);
+  _UnnecessaryGetterOverrideVisitor(super.rule);
 
   @override
-  ExecutableElement? getInheritedElement(MethodDeclaration node) =>
-      DartTypeUtilities.lookUpInheritedConcreteGetter(node);
+  ExecutableElement? getInheritedElement(MethodDeclaration node) {
+    var element = node.declaredElement;
+    if (element == null) return null;
+    var enclosingElement = element.enclosingElement;
+    if (enclosingElement is! InterfaceElement) return null;
+    return enclosingElement.thisType.lookUpGetter2(
+      element.name,
+      element.library,
+      concrete: true,
+      inherited: true,
+    );
+  }
 
   @override
   void visitPropertyAccess(PropertyAccess node) {
-    if (node.propertyName.staticElement == inheritedMethod) {
+    if (node.propertyName.name == _inheritedMethod.name) {
       node.target?.accept(this);
     }
   }
@@ -197,18 +244,28 @@
 
 class _UnnecessaryMethodOverrideVisitor
     extends _AbstractUnnecessaryOverrideVisitor {
-  _UnnecessaryMethodOverrideVisitor(LintRule rule) : super(rule);
+  _UnnecessaryMethodOverrideVisitor(super.rule);
 
   @override
-  ExecutableElement? getInheritedElement(node) =>
-      DartTypeUtilities.lookUpInheritedMethod(node);
+  ExecutableElement? getInheritedElement(node) {
+    var element = node.declaredElement;
+    if (element == null) return null;
+    var enclosingElement = element.enclosingElement;
+    if (enclosingElement is! InterfaceElement) return null;
+    return enclosingElement.thisType.lookUpMethod2(
+      node.name.lexeme,
+      element.library,
+      concrete: true,
+      inherited: true,
+    );
+  }
 
   @override
   void visitMethodInvocation(MethodInvocation node) {
     var declarationParameters = declaration.parameters;
     if (declarationParameters != null &&
-        node.methodName.staticElement == inheritedMethod &&
-        DartTypeUtilities.matchesArgumentsWithParameters(
+        node.methodName.name == _inheritedMethod.name &&
+        argumentsMatchParameters(
             node.argumentList.arguments, declarationParameters.parameters)) {
       node.target?.accept(this);
     }
@@ -217,21 +274,30 @@
 
 class _UnnecessaryOperatorOverrideVisitor
     extends _AbstractUnnecessaryOverrideVisitor {
-  _UnnecessaryOperatorOverrideVisitor(LintRule rule) : super(rule);
+  _UnnecessaryOperatorOverrideVisitor(super.rule);
 
   @override
-  ExecutableElement? getInheritedElement(node) =>
-      DartTypeUtilities.lookUpInheritedConcreteMethod(node);
+  ExecutableElement? getInheritedElement(node) {
+    var element = node.declaredElement;
+    if (element == null) return null;
+    var enclosingElement = element.enclosingElement;
+    if (enclosingElement is! InterfaceElement) return null;
+    return enclosingElement.thisType.lookUpMethod2(
+      element.name,
+      element.library,
+      concrete: true,
+      inherited: true,
+    );
+  }
 
   @override
   void visitBinaryExpression(BinaryExpression node) {
     var parameters = declaration.parameters?.parameters;
-    if (node.operator.type == declaration.name.token.type &&
+    if (node.operator.type == declaration.name.type &&
         parameters != null &&
         parameters.length == 1 &&
-        parameters.first.identifier?.staticElement ==
-            DartTypeUtilities.getCanonicalElementFromIdentifier(
-                node.rightOperand)) {
+        parameters.first.declaredElement ==
+            node.rightOperand.canonicalElement) {
       var leftPart = node.leftOperand.unParenthesized;
       if (leftPart is SuperExpression) {
         visitSuperExpression(leftPart);
@@ -243,7 +309,7 @@
   void visitPrefixExpression(PrefixExpression node) {
     var parameters = declaration.parameters?.parameters;
     if (parameters != null &&
-        node.operator.type == declaration.name.token.type &&
+        node.operator.type == declaration.name.type &&
         parameters.isEmpty) {
       var operand = node.operand.unParenthesized;
       if (operand is SuperExpression) {
@@ -255,23 +321,32 @@
 
 class _UnnecessarySetterOverrideVisitor
     extends _AbstractUnnecessaryOverrideVisitor {
-  _UnnecessarySetterOverrideVisitor(LintRule rule) : super(rule);
+  _UnnecessarySetterOverrideVisitor(super.rule);
 
   @override
-  ExecutableElement? getInheritedElement(node) =>
-      DartTypeUtilities.lookUpInheritedConcreteSetter(node);
+  ExecutableElement? getInheritedElement(node) {
+    var element = node.declaredElement;
+    if (element == null) return null;
+    var enclosingElement = element.enclosingElement;
+    if (enclosingElement is! InterfaceElement) return null;
+    return enclosingElement.thisType.lookUpSetter2(
+      node.name.lexeme,
+      element.library,
+      concrete: true,
+      inherited: true,
+    );
+  }
 
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
     var parameters = declaration.parameters?.parameters;
     if (parameters != null &&
         parameters.length == 1 &&
-        parameters.first.identifier?.staticElement ==
-            DartTypeUtilities.getCanonicalElementFromIdentifier(
-                node.rightHandSide)) {
+        parameters.first.declaredElement ==
+            node.rightHandSide.canonicalElement) {
       var leftPart = node.leftHandSide.unParenthesized;
       if (leftPart is PropertyAccess) {
-        if (node.writeElement == inheritedMethod) {
+        if (node.writeElement?.name == _inheritedMethod.name) {
           leftPart.target?.accept(this);
         }
       }
diff --git a/lib/src/rules/unnecessary_parenthesis.dart b/lib/src/rules/unnecessary_parenthesis.dart
index fdcd210..82ccd65 100644
--- a/lib/src/rules/unnecessary_parenthesis.dart
+++ b/lib/src/rules/unnecessary_parenthesis.dart
@@ -7,25 +7,39 @@
 
 import '../analyzer.dart';
 
-const _desc = r'Unnecessary parenthesis can be removed.';
+const _desc = r'Unnecessary parentheses can be removed.';
 
 const _details = r'''
-
-**AVOID** using parenthesis when not needed.
-
-**GOOD:**
-```dart
-a = b;
-```
+**AVOID** using parentheses when not needed.
 
 **BAD:**
 ```dart
 a = (b);
 ```
 
+**GOOD:**
+```dart
+a = b;
+```
+
+Parentheses are considered unnecessary if they do not change the meaning of the
+code and they do not improve the readability of the code. The goal is not to
+force all developers to maintain the expression precedence table in their heads,
+which is why the second condition is included. Examples of this condition
+include:
+
+* cascade expressions - it is sometimes not clear what the target of a cascade
+  expression is, especially with assignments, or nested cascades. For example,
+  the expression `a.b = (c..d)`.
+* expressions with whitespace between tokens - it can look very strange to see
+  an expression like `!await foo` which is valid and equivalent to
+  `!(await foo)`.
+* logical expressions - parentheses can improve the readability of the implicit
+  grouping defined by precedence. For example, the expression
+  `(a && b) || c && d`.
 ''';
 
-class UnnecessaryParenthesis extends LintRule implements NodeLintRule {
+class UnnecessaryParenthesis extends LintRule {
   UnnecessaryParenthesis()
       : super(
             name: 'unnecessary_parenthesis',
@@ -48,9 +62,9 @@
 
   @override
   void visitParenthesizedExpression(ParenthesizedExpression node) {
+    var parent = node.parent;
     var expression = node.expression;
     if (expression is SimpleIdentifier) {
-      var parent = node.parent;
       if (parent is PropertyAccess) {
         if (parent.propertyName.name == 'hashCode' ||
             parent.propertyName.name == 'runtimeType') {
@@ -68,14 +82,30 @@
       return;
     }
 
-    var parent = node.parent;
+    // https://github.com/dart-lang/linter/issues/2944
+    if (expression is FunctionExpression) {
+      if (parent is MethodInvocation ||
+          parent is PropertyAccess ||
+          parent is BinaryExpression ||
+          parent is IndexExpression) {
+        return;
+      }
+    }
+
+    if (expression is ConstructorReference) {
+      if (parent is! FunctionExpressionInvocation ||
+          parent.typeArguments == null) {
+        rule.reportLint(node);
+        return;
+      }
+    }
 
     if (parent is ParenthesizedExpression) {
       rule.reportLint(node);
       return;
     }
 
-    // `a..b=(c..d)` is OK.
+    // `a..b = (c..d)` is OK.
     if (expression is CascadeExpression ||
         node.thisOrAncestorMatching(
                 (n) => n is Statement || n is CascadeExpression)
@@ -95,6 +125,8 @@
       if (parent is BinaryExpression) return;
       if (parent is ConditionalExpression) return;
       if (parent is CascadeExpression) return;
+      if (parent is AsExpression) return;
+      if (parent is IsExpression) return;
       if (parent is FunctionExpressionInvocation) {
         if (expression is PrefixedIdentifier) {
           rule.reportLint(node);
@@ -105,8 +137,9 @@
       // A prefix expression (! or -) can have an argument wrapped in
       // "unnecessary" parens if that argument has potentially confusing
       // whitespace after its first token.
-      if (parent is PrefixExpression &&
-          _expressionStartsWithWhitespace(expression)) return;
+      if (parent is PrefixExpression && node.expression.startsWithWhitespace) {
+        return;
+      }
 
       // Another case of the above exception, something like
       // `!(const [7]).contains(5);`, where the _parent's_ parent is the
@@ -115,18 +148,13 @@
         var target = parent.target;
         if (parent.parent is PrefixExpression &&
             target == node &&
-            _expressionStartsWithWhitespace(expression)) return;
+            node.expression.startsWithWhitespace) return;
       }
 
       // Something like `({1, 2, 3}).forEach(print);`.
       // The parens cannot be removed because then the curly brackets are not
       // interpreted as a set-or-map literal.
       if (parent is PropertyAccess || parent is MethodInvocation) {
-        var target = (parent as dynamic).target;
-        if (target == node &&
-            expression is SetOrMapLiteral &&
-            parent.parent is ExpressionStatement) return;
-
         // TODO an API to the AST for better usage
         // Precedence isn't sufficient (e.g. PostfixExpression requires parenthesis)
         if (expression is PropertyAccess ||
@@ -135,6 +163,9 @@
           rule.reportLint(node);
         }
       }
+      if (node.wouldBeParsedAsStatementBlock) {
+        return;
+      }
 
       if (parent.precedence < expression.precedence) {
         rule.reportLint(node);
@@ -150,26 +181,6 @@
     node.accept(containsFunctionExpressionVisitor);
     return containsFunctionExpressionVisitor.hasFunctionExpression;
   }
-
-  /// Returns whether [node] "starts" with whitespace.
-  ///
-  /// That is, is there definitely whitespace after the first token in [node]?
-  bool _expressionStartsWithWhitespace(Expression? node) =>
-      // As in, `!(await foo)`.
-      node is AwaitExpression ||
-      // As in, `!(new Foo())`.
-      (node is InstanceCreationExpression && node.keyword != null) ||
-      // No TypedLiteral (ListLiteral, MapLiteral, SetLiteral) accepts `-` or
-      // `!` as a prefix operator, but this method can be called recursively,
-      // so this catches things like `!(const [].contains(42))`.
-      (node is TypedLiteral && node.constKeyword != null) ||
-      // As in, `!(const List(3).contains(7))`, and chains like
-      // `-(new List(3).skip(1).take(3).skip(1).length)`.
-      (node is MethodInvocation &&
-          _expressionStartsWithWhitespace(node.target)) ||
-      // As in, `-(new List(3).length)`, and chains like
-      // `-(new List(3).length.bitLength.bitLength)`.
-      (node is PropertyAccess && _expressionStartsWithWhitespace(node.target));
 }
 
 class _ContainsFunctionExpressionVisitor extends UnifyingAstVisitor<void> {
@@ -187,3 +198,48 @@
     }
   }
 }
+
+extension on ParenthesizedExpression {
+  /// Returns whether a parser would attempt to parse `this` as a statement
+  /// block if the parentheses were removed.
+  ///
+  /// The two components that make this true are:
+  /// * the parenthesized expression is a [SetOrMapLiteral] (starting with `{`),
+  /// * the open parenthesis of this expression is the first token of an
+  ///   [ExpressionStatement].
+  bool get wouldBeParsedAsStatementBlock {
+    if (expression is! SetOrMapLiteral) {
+      return false;
+    }
+    var exprStatementAncestor = thisOrAncestorOfType<ExpressionStatement>();
+    if (exprStatementAncestor == null) {
+      return false;
+    }
+    return exprStatementAncestor.beginToken == leftParenthesis;
+  }
+}
+
+extension on Expression? {
+  /// Returns whether this "starts" with whitespace.
+  ///
+  /// That is, is there definitely whitespace after the first token?
+  bool get startsWithWhitespace {
+    var self = this;
+    return
+        // As in, `!(await foo)`.
+        self is AwaitExpression ||
+            // As in, `!(new Foo())`.
+            (self is InstanceCreationExpression && self.keyword != null) ||
+            // No TypedLiteral (ListLiteral, MapLiteral, SetLiteral) accepts `-`
+            // or `!` as a prefix operator, but this method can be called
+            // rescursively, so this catches things like
+            // `!(const [].contains(42))`.
+            (self is TypedLiteral && self.constKeyword != null) ||
+            // As in, `!(const List(3).contains(7))`, and chains like
+            // `-(new List(3).skip(1).take(3).skip(1).length)`.
+            (self is MethodInvocation && self.target.startsWithWhitespace) ||
+            // As in, `-(new List(3).length)`, and chains like
+            // `-(new List(3).length.bitLength.bitLength)`.
+            (self is PropertyAccess && self.target.startsWithWhitespace);
+  }
+}
diff --git a/lib/src/rules/unnecessary_raw_strings.dart b/lib/src/rules/unnecessary_raw_strings.dart
index f2fb7b1..7ed3d36 100644
--- a/lib/src/rules/unnecessary_raw_strings.dart
+++ b/lib/src/rules/unnecessary_raw_strings.dart
@@ -10,7 +10,6 @@
 const _desc = r'Unnecessary raw string.';
 
 const _details = r'''
-
 Use raw string only when needed.
 
 **BAD:**
@@ -27,7 +26,7 @@
 
 ''';
 
-class UnnecessaryRawStrings extends LintRule implements NodeLintRule {
+class UnnecessaryRawStrings extends LintRule {
   UnnecessaryRawStrings()
       : super(
             name: 'unnecessary_raw_strings',
diff --git a/lib/src/rules/unnecessary_statements.dart b/lib/src/rules/unnecessary_statements.dart
index 88598bb..f140bbc 100644
--- a/lib/src/rules/unnecessary_statements.dart
+++ b/lib/src/rules/unnecessary_statements.dart
@@ -12,7 +12,6 @@
 const _desc = r'Avoid using unnecessary statements.';
 
 const _details = r'''
-
 **AVOID** using unnecessary statements.
 
 Statements which have no clear effect are usually unnecessary, or should be
@@ -46,7 +45,11 @@
 
 ''';
 
-class UnnecessaryStatements extends LintRule implements NodeLintRule {
+class UnnecessaryStatements extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_statements', 'Unnecessary statement.',
+      correctionMessage: 'Try completing the statement or breaking it up.');
+
   UnnecessaryStatements()
       : super(
             name: 'unnecessary_statements',
@@ -55,10 +58,12 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(_ReportNoClearEffectVisitor(this));
-    registry.addAsExpression(this, visitor);
     registry.addExpressionStatement(this, visitor);
     registry.addForStatement(this, visitor);
     registry.addCascadeExpression(this, visitor);
diff --git a/lib/src/rules/unnecessary_string_escapes.dart b/lib/src/rules/unnecessary_string_escapes.dart
index 8fee977..0853d57 100644
--- a/lib/src/rules/unnecessary_string_escapes.dart
+++ b/lib/src/rules/unnecessary_string_escapes.dart
@@ -11,7 +11,6 @@
 const _desc = r'Remove unnecessary backslashes in strings.';
 
 const _details = r'''
-
 Remove unnecessary backslashes in strings.
 
 **BAD:**
@@ -28,7 +27,11 @@
 
 ''';
 
-class UnnecessaryStringEscapes extends LintRule implements NodeLintRule {
+class UnnecessaryStringEscapes extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_string_escapes', 'Unnecessary escape in string literal.',
+      correctionMessage: "Remove the '\\' escape.");
+
   UnnecessaryStringEscapes()
       : super(
             name: 'unnecessary_string_escapes',
@@ -37,6 +40,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -46,36 +52,26 @@
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
+  /// The special escaped chars listed in language specification
+  static const allowedEscapedChars = [
+    '"',
+    "'",
+    r'$',
+    r'\',
+    'n',
+    'r',
+    'f',
+    'b',
+    't',
+    'v',
+    'x',
+    'u',
+  ];
+
   final LintRule rule;
 
   _Visitor(this.rule);
 
-  @override
-  void visitSimpleStringLiteral(SimpleStringLiteral node) {
-    if (node.isRaw) return;
-
-    visitLexeme(
-      node.literal,
-      isSingleQuoted: node.isSingleQuoted,
-      isMultiline: node.isMultiline,
-      contentsOffset: node.contentsOffset,
-      contentsEnd: node.contentsEnd,
-    );
-  }
-
-  @override
-  void visitStringInterpolation(StringInterpolation node) {
-    for (var element in node.elements.whereType<InterpolationString>()) {
-      visitLexeme(
-        element.contents,
-        isSingleQuoted: node.isSingleQuoted,
-        isMultiline: node.isMultiline,
-        contentsOffset: element.contentsOffset,
-        contentsEnd: element.contentsEnd,
-      );
-    }
-  }
-
   void visitLexeme(
     Token token, {
     required bool isSingleQuoted,
@@ -125,19 +121,29 @@
     checkPendingQuotes();
   }
 
-  /// The special escaped chars listed in language specification
-  static const allowedEscapedChars = [
-    '"',
-    "'",
-    r'$',
-    r'\',
-    'n',
-    'r',
-    'f',
-    'b',
-    't',
-    'v',
-    'x',
-    'u',
-  ];
+  @override
+  void visitSimpleStringLiteral(SimpleStringLiteral node) {
+    if (node.isRaw) return;
+
+    visitLexeme(
+      node.literal,
+      isSingleQuoted: node.isSingleQuoted,
+      isMultiline: node.isMultiline,
+      contentsOffset: node.contentsOffset,
+      contentsEnd: node.contentsEnd,
+    );
+  }
+
+  @override
+  void visitStringInterpolation(StringInterpolation node) {
+    for (var element in node.elements.whereType<InterpolationString>()) {
+      visitLexeme(
+        element.contents,
+        isSingleQuoted: node.isSingleQuoted,
+        isMultiline: node.isMultiline,
+        contentsOffset: element.contentsOffset,
+        contentsEnd: element.contentsEnd,
+      );
+    }
+  }
 }
diff --git a/lib/src/rules/unnecessary_string_interpolations.dart b/lib/src/rules/unnecessary_string_interpolations.dart
index a224d39..d33a74a 100644
--- a/lib/src/rules/unnecessary_string_interpolations.dart
+++ b/lib/src/rules/unnecessary_string_interpolations.dart
@@ -11,8 +11,7 @@
 const _desc = r'Unnecessary string interpolation.';
 
 const _details = r'''
-
-Don't use string interpolation if there's only a string expression in it.
+**DON'T** use string interpolation if there's only a string expression in it.
 
 **BAD:**
 ```dart
@@ -28,7 +27,12 @@
 
 ''';
 
-class UnnecessaryStringInterpolations extends LintRule implements NodeLintRule {
+class UnnecessaryStringInterpolations extends LintRule {
+  static const LintCode code = LintCode('unnecessary_string_interpolations',
+      'Unnecessary use of string interpolation.',
+      correctionMessage:
+          'Try replacing the string literal with the variable name.');
+
   UnnecessaryStringInterpolations()
       : super(
             name: 'unnecessary_string_interpolations',
@@ -37,6 +41,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -53,7 +60,7 @@
   void visitStringInterpolation(StringInterpolation node) {
     if (node.parent is AdjacentStrings) return;
     if (node.elements.length == 3) {
-      var start = node.elements[0] as InterpolationString;
+      var start = node.elements.first as InterpolationString;
       var interpolation = node.elements[1] as InterpolationExpression;
       var end = node.elements[2] as InterpolationString;
       if (start.value.isEmpty && end.value.isEmpty) {
diff --git a/lib/src/rules/unnecessary_this.dart b/lib/src/rules/unnecessary_this.dart
index 23b404e..d4d5405 100644
--- a/lib/src/rules/unnecessary_this.dart
+++ b/lib/src/rules/unnecessary_this.dart
@@ -12,7 +12,6 @@
 const _desc = r"Don't access members with `this` unless avoiding shadowing.";
 
 const _details = r'''
-
 From the [style guide](https://dart.dev/guides/language/effective-dart/style/):
 
 **DON'T** use `this` when not needed to avoid shadowing.
@@ -49,7 +48,11 @@
 
 ''';
 
-class UnnecessaryThis extends LintRule implements NodeLintRule {
+class UnnecessaryThis extends LintRule {
+  static const LintCode code = LintCode(
+      'unnecessary_this', "Unnecessary 'this.' qualifier.",
+      correctionMessage: "Try removing 'this.'.");
+
   UnnecessaryThis()
       : super(
             name: 'unnecessary_this',
@@ -58,6 +61,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
diff --git a/lib/src/rules/unnecessary_to_list_in_spreads.dart b/lib/src/rules/unnecessary_to_list_in_spreads.dart
new file mode 100644
index 0000000..63e2aa6
--- /dev/null
+++ b/lib/src/rules/unnecessary_to_list_in_spreads.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+import '../extensions.dart';
+
+const _desc = r'Unnecessary toList() in spreads.';
+
+const _details = r'''
+Unnecessary `toList()` in spreads.
+
+**BAD:**
+```dart
+children: <Widget>[
+  ...['foo', 'bar', 'baz'].map((String s) => Text(s)).toList(),
+]
+```
+
+**GOOD:**
+```dart
+children: <Widget>[
+  ...['foo', 'bar', 'baz'].map((String s) => Text(s)),
+]
+```
+
+''';
+
+class UnnecessaryToListInSpreads extends LintRule {
+  UnnecessaryToListInSpreads()
+      : super(
+          name: 'unnecessary_to_list_in_spreads',
+          description: _desc,
+          details: _details,
+          group: Group.style,
+        );
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addSpreadElement(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitSpreadElement(SpreadElement node) {
+    var expression = node.expression;
+    if (expression is! MethodInvocation) {
+      return;
+    }
+    var target = expression.target;
+    if (expression.methodName.name == 'toList' &&
+        target != null &&
+        target.staticType.implementsInterface('Iterable', 'dart.core')) {
+      rule.reportLint(expression.methodName);
+    }
+  }
+}
diff --git a/lib/src/rules/unreachable_from_main.dart b/lib/src/rules/unreachable_from_main.dart
new file mode 100644
index 0000000..7ef4713
--- /dev/null
+++ b/lib/src/rules/unreachable_from_main.dart
@@ -0,0 +1,240 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+
+import '../analyzer.dart';
+
+const _desc = 'Unreachable top-level members in executable libraries.';
+
+const _details = r'''
+Top-level members in an executable library should be used directly inside this
+library.  An executable library is a library that contains a `main` top-level
+function or that contains a top-level function annotated with
+`@pragma('vm:entry-point')`).  Executable libraries are not usually imported
+and it's better to avoid defining unused members.
+
+This rule assumes that an executable library isn't imported by other files
+except to execute its `main` function.
+
+**BAD:**
+
+```dart
+main() {}
+void f() {}
+```
+
+**GOOD:**
+
+```dart
+main() {
+  f();
+}
+void f() {}
+```
+
+''';
+
+class UnreachableFromMain extends LintRule {
+  UnreachableFromMain()
+      : super(
+          name: 'unreachable_from_main',
+          description: _desc,
+          details: _details,
+          group: Group.style,
+          maturity: Maturity.experimental,
+        );
+
+  @override
+  void registerNodeProcessors(
+    NodeLintRegistry registry,
+    LinterContext context,
+  ) {
+    var visitor = _Visitor(this);
+    registry.addCompilationUnit(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  _Visitor(this.rule);
+
+  final LintRule rule;
+
+  @override
+  void visitCompilationUnit(CompilationUnit node) {
+    // TODO(a14n): add support of libs with parts
+    if (node.directives.whereType<PartOfDirective>().isNotEmpty) return;
+    if (node.directives.whereType<PartDirective>().isNotEmpty) return;
+
+    var topDeclarations = node.declarations
+        .expand((e) => [
+              if (e is TopLevelVariableDeclaration)
+                ...e.variables.variables
+              else
+                e,
+            ])
+        .toSet();
+
+    var entryPoints = topDeclarations.where(_isEntryPoint).toList();
+    if (entryPoints.isEmpty) return;
+
+    var declarationByElement = <Element, Declaration>{};
+    for (var declaration in topDeclarations) {
+      var element = declaration.declaredElement;
+      if (element != null) {
+        if (element is TopLevelVariableElement) {
+          declarationByElement[element] = declaration;
+          var getter = element.getter;
+          if (getter != null) declarationByElement[getter] = declaration;
+          var setter = element.setter;
+          if (setter != null) declarationByElement[setter] = declaration;
+        } else {
+          declarationByElement[element] = declaration;
+        }
+      }
+    }
+
+    // The set of the declarations which each top-level declaration references.
+    var dependencies = <Declaration, Set<Declaration>>{};
+    for (var declaration in topDeclarations) {
+      var visitor = _IdentifierVisitor(declarationByElement);
+      declaration.accept(visitor);
+      dependencies[declaration] = visitor.declarations;
+    }
+
+    var usedMembers = entryPoints.toSet();
+    // The following variable will be used to visit every reachable declaration
+    // starting from entry-points. At every loop an element is removed. This
+    // element is marked as used and we add its dependencies in the declaration
+    // list to traverse. Once this list is empty `usedMembers` contains every
+    // declarations reachable from an entry-point.
+    var declarationsToCheck = Queue.of(usedMembers);
+    while (declarationsToCheck.isNotEmpty) {
+      var declaration = declarationsToCheck.removeLast();
+      for (var dep in dependencies[declaration]!) {
+        if (usedMembers.add(dep)) {
+          declarationsToCheck.add(dep);
+        }
+      }
+    }
+
+    var unusedMembers = topDeclarations.difference(usedMembers).where((e) {
+      var element = e.declaredElement;
+      return element != null &&
+          element.isPublic &&
+          !element.hasVisibleForTesting;
+    });
+
+    for (var member in unusedMembers) {
+      if (member is NamedCompilationUnitMember) {
+        rule.reportLintForToken(member.name);
+      } else if (member is VariableDeclaration) {
+        rule.reportLintForToken(member.name);
+      } else if (member is ExtensionDeclaration) {
+        rule.reportLintForToken(
+            member.name ?? member.firstTokenAfterCommentAndMetadata);
+      } else {
+        rule.reportLintForToken(member.firstTokenAfterCommentAndMetadata);
+      }
+    }
+  }
+
+  bool _isEntryPoint(Declaration e) =>
+      e is FunctionDeclaration &&
+      (e.name.lexeme == 'main' || e.metadata.any(_isPragmaVmEntry));
+
+  bool _isPragmaVmEntry(Annotation annotation) {
+    if (!annotation.isPragma) return false;
+    var value = annotation.elementAnnotation?.computeConstantValue();
+    if (value == null) return false;
+    var name = value.getField('name');
+    return name != null &&
+        name.hasKnownValue &&
+        name.toStringValue() == 'vm:entry-point';
+  }
+}
+
+/// A visitor which gathers the declarations of the identifiers it visits.
+class _IdentifierVisitor extends RecursiveAstVisitor {
+  Map<Element, Declaration> declarationMap;
+
+  Set<Declaration> declarations = {};
+
+  _IdentifierVisitor(this.declarationMap);
+
+  @override
+  void visitAssignmentExpression(AssignmentExpression node) {
+    _visitCompoundAssignmentExpression(node);
+    super.visitAssignmentExpression(node);
+  }
+
+  @override
+  void visitPostfixExpression(PostfixExpression node) {
+    _visitCompoundAssignmentExpression(node);
+    super.visitPostfixExpression(node);
+  }
+
+  @override
+  void visitPrefixExpression(PrefixExpression node) {
+    _visitCompoundAssignmentExpression(node);
+    super.visitPrefixExpression(node);
+  }
+
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    var e = node.staticElement;
+    if (e != null) {
+      _addDeclaration(e);
+    }
+    super.visitSimpleIdentifier(node);
+  }
+
+  void _visitCompoundAssignmentExpression(CompoundAssignmentExpression node) {
+    var readElement = node.readElement;
+    if (readElement != null) {
+      _addDeclaration(readElement);
+    }
+    var writeElement = node.writeElement;
+    if (writeElement != null) {
+      _addDeclaration(writeElement);
+    }
+  }
+
+  /// Adds the declaration of the top-level element which contains [element] to
+  /// [declarations], if it is found in [declarationMap].
+  void _addDeclaration(Element element) {
+    var enclosingElement = element.thisOrAncestorMatching((a) =>
+        a.enclosingElement == null ||
+        a.enclosingElement is CompilationUnitElement);
+    var enclosingDeclaration = declarationMap[enclosingElement];
+    if (enclosingDeclaration != null) {
+      declarations.add(enclosingDeclaration);
+    }
+  }
+}
+
+extension on Element {
+  bool get isPragma => (library?.isDartCore ?? false) && name == 'pragma';
+}
+
+extension on Annotation {
+  bool get isPragma {
+    var element = elementAnnotation?.element;
+    DartType type;
+    if (element is ConstructorElement) {
+      type = element.returnType;
+    } else if (element is PropertyAccessorElement && element.isGetter) {
+      type = element.returnType;
+    } else {
+      // Dunno what this is.
+      return false;
+    }
+    return type is InterfaceType && type.element.isPragma;
+  }
+}
diff --git a/lib/src/rules/unrelated_type_equality_checks.dart b/lib/src/rules/unrelated_type_equality_checks.dart
index ba5404c..decdc00 100644
--- a/lib/src/rules/unrelated_type_equality_checks.dart
+++ b/lib/src/rules/unrelated_type_equality_checks.dart
@@ -7,15 +7,16 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:collection/collection.dart';
 
 import '../analyzer.dart';
+import '../extensions.dart';
 import '../util/dart_type_utilities.dart';
 
 const _desc =
     r'Equality operator `==` invocation with references of unrelated types.';
 
 const _details = r'''
-
 **DON'T** Compare references of unrelated types for equality.
 
 Comparing references of a type where neither is a subtype of the other most
@@ -139,24 +140,30 @@
   if (leftType == null || rightType == null) {
     return false;
   }
-  return !DartTypeUtilities.isNullLiteral(left) &&
-      !DartTypeUtilities.isNullLiteral(right) &&
-      DartTypeUtilities.unrelatedTypes(typeSystem, leftType, rightType) &&
+  return !left.isNullLiteral &&
+      !right.isNullLiteral &&
+      typesAreUnrelated(typeSystem, leftType, rightType) &&
       !(_isFixNumIntX(leftType) && _isCoreInt(rightType));
 }
 
 bool _isCoreInt(DartType type) => type.isDartCoreInt;
 
 bool _isFixNumIntX(DartType type) {
-  if (type is! InterfaceType) {
-    return false;
-  }
-  Element element = type.element;
-  return (element.name == 'Int32' || element.name == 'Int64') &&
-      element.library?.name == 'fixnum';
+  // todo(pq): add tests that ensure this predicate works with fixnum >= 1.1.0-dev
+  // See: https://github.com/dart-lang/linter/issues/3868
+  if (type is! InterfaceType) return false;
+  InterfaceElement element = type.element;
+  if (element.name != 'Int32' && element.name != 'Int64') return false;
+  var uri = element.library.source.uri;
+  if (!uri.isScheme('package')) return false;
+  return uri.pathSegments.firstOrNull == 'fixnum';
 }
 
-class UnrelatedTypeEqualityChecks extends LintRule implements NodeLintRule {
+class UnrelatedTypeEqualityChecks extends LintRule {
+  static const LintCode code = LintCode('unrelated_type_equality_checks',
+      "Neither type of the operands of '==' is a subtype of the other.",
+      correctionMessage: 'Try changing one or both of the operands.');
+
   UnrelatedTypeEqualityChecks()
       : super(
             name: 'unrelated_type_equality_checks',
@@ -165,6 +172,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context.typeSystem);
diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 4aef87b..18ae232 100644
--- a/lib/src/rules/unsafe_html.dart
+++ b/lib/src/rules/unsafe_html.dart
@@ -8,18 +8,17 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
-const _descPrefix = r'Avoid unsafe HTML APIs';
 const _desc = '$_descPrefix.';
+const _descPrefix = r'Avoid unsafe HTML APIs';
 
 const _details = r'''
-
 **AVOID**
 
 * assigning directly to the `href` field of an AnchorElement
-* assigning directly to the `src` field of an EmbedElement, IFrameElement,
-  ImageElement, or ScriptElement
+* assigning directly to the `src` field of an EmbedElement, IFrameElement, or
+  ScriptElement
 * assigning directly to the `srcdoc` field of an IFrameElement
 * calling the `createFragment` method of Element
 * calling the `open` method of Window
@@ -34,13 +33,22 @@
 ```
 ''';
 
-extension on DartType? {
-  /// Returns whether this type extends [className] from the dart:html library.
-  bool extendsDartHtmlClass(String className) =>
-      DartTypeUtilities.extendsClass(this, className, 'dart.dom.html');
-}
+class UnsafeHtml extends LintRule {
+  // static const LintCode attributeCode = LintCode(
+  //     'unsafe_html', "Assigning to the attribute '{0}' is unsafe.",
+  //     correctionMessage: 'Try finding a different way to implement the page.',
+  //     uniqueName: 'LintCode.unsafe_html_attribute');
 
-class UnsafeHtml extends LintRule implements NodeLintRule {
+  // static const LintCode methodCode = LintCode(
+  //     'unsafe_html', "Invoking the method '{0}' is unsafe.",
+  //     correctionMessage: 'Try finding a different way to implement the page.',
+  //     uniqueName: 'LintCode.unsafe_html_method');
+
+  // static const LintCode constructorCode = LintCode(
+  //     'unsafe_html', "Invoking the constructor '{0}' is unsafe.",
+  //     correctionMessage: 'Try finding a different way to implement the page.',
+  //     uniqueName: 'LintCode.unsafe_html_constructor');
+
   UnsafeHtml()
       : super(
             name: 'unsafe_html',
@@ -49,21 +57,20 @@
             group: Group.errors);
 
   @override
-  void registerNodeProcessors(
-      NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this);
-    registry.addAssignmentExpression(this, visitor);
-    registry.addInstanceCreationExpression(this, visitor);
-    registry.addFunctionExpressionInvocation(this, visitor);
-    registry.addMethodInvocation(this, visitor);
-  }
-
-  @override
   List<LintCode> get lintCodes => [
         _Visitor.unsafeAttributeCode,
         _Visitor.unsafeMethodCode,
         _Visitor.unsafeConstructorCode
       ];
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addAssignmentExpression(this, visitor);
+    registry.addInstanceCreationExpression(this, visitor);
+    registry.addMethodInvocation(this, visitor);
+  }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
@@ -71,6 +78,7 @@
   // single-quotes to match the convention in the analyzer and linter packages.
   // This requires some coordination within Google, as various allow-lists are
   // keyed on the exact text of the LintCode message.
+  // Proposed replacements are commented out in `UnsafeHtml`.
   static const unsafeAttributeCode = SecurityLintCode(
     'unsafe_html',
     '$_descPrefix (assigning "{0}" attribute).',
@@ -109,34 +117,6 @@
     }
   }
 
-  void _checkAssignment(DartType? type, SimpleIdentifier property,
-      AssignmentExpression assignment) {
-    if (type == null) return;
-
-    // It is more efficient to check the setter's name before checking whether
-    // the target is an interesting type.
-    if (property.name == 'href') {
-      if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
-        rule.reportLint(assignment,
-            arguments: ['href'], errorCode: unsafeAttributeCode);
-      }
-    } else if (property.name == 'src') {
-      if (type.isDynamic ||
-          type.extendsDartHtmlClass('EmbedElement') ||
-          type.extendsDartHtmlClass('IFrameElement') ||
-          type.extendsDartHtmlClass('ImageElement') ||
-          type.extendsDartHtmlClass('ScriptElement')) {
-        rule.reportLint(assignment,
-            arguments: ['src'], errorCode: unsafeAttributeCode);
-      }
-    } else if (property.name == 'srcdoc') {
-      if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
-        rule.reportLint(assignment,
-            arguments: ['srcdoc'], errorCode: unsafeAttributeCode);
-      }
-    }
-  }
-
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     var type = node.staticType;
@@ -191,4 +171,37 @@
           arguments: ['open', 'Window'], errorCode: unsafeMethodCode);
     }
   }
+
+  void _checkAssignment(DartType? type, SimpleIdentifier property,
+      AssignmentExpression assignment) {
+    if (type == null) return;
+
+    // It is more efficient to check the setter's name before checking whether
+    // the target is an interesting type.
+    if (property.name == 'href') {
+      if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
+        rule.reportLint(assignment,
+            arguments: ['href'], errorCode: unsafeAttributeCode);
+      }
+    } else if (property.name == 'src') {
+      if (type.isDynamic ||
+          type.extendsDartHtmlClass('EmbedElement') ||
+          type.extendsDartHtmlClass('IFrameElement') ||
+          type.extendsDartHtmlClass('ScriptElement')) {
+        rule.reportLint(assignment,
+            arguments: ['src'], errorCode: unsafeAttributeCode);
+      }
+    } else if (property.name == 'srcdoc') {
+      if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
+        rule.reportLint(assignment,
+            arguments: ['srcdoc'], errorCode: unsafeAttributeCode);
+      }
+    }
+  }
+}
+
+extension on DartType? {
+  /// Returns whether this type extends [className] from the dart:html library.
+  bool extendsDartHtmlClass(String className) =>
+      extendsClass(className, 'dart.dom.html');
 }
diff --git a/lib/src/rules/use_build_context_synchronously.dart b/lib/src/rules/use_build_context_synchronously.dart
index 5890bdf..328b80c 100644
--- a/lib/src/rules/use_build_context_synchronously.dart
+++ b/lib/src/rules/use_build_context_synchronously.dart
@@ -14,21 +14,14 @@
 const _desc = r'Do not use BuildContexts across async gaps.';
 
 const _details = r'''
-**DO NOT** use BuildContext across asynchronous gaps.
+**DON'T** use BuildContext across asynchronous gaps.
 
 Storing `BuildContext` for later usage can easily lead to difficult to diagnose
 crashes. Asynchronous gaps are implicitly storing `BuildContext` and are some of
 the easiest to overlook when writing code.
 
-When a `BuildContext` is used from a `StatefulWidget`, the `mounted` property
-must be checked after an asynchronous gap.
-
-**GOOD:**
-```dart
-void onButtonTapped(BuildContext context) {
-  Navigator.of(context).pop();
-}
-```
+When a `BuildContext` is used, its `mounted` property must be checked after an
+asynchronous gap.
 
 **BAD:**
 ```dart
@@ -40,20 +33,28 @@
 
 **GOOD:**
 ```dart
-class _MyWidgetState extends State<MyWidget> {
-  ...
+void onButtonTapped(BuildContext context) {
+  Navigator.of(context).pop();
+}
+```
 
-  void onButtonTapped() async {
-    await Future.delayed(const Duration(seconds: 1));
+**GOOD:**
+```dart
+void onButtonTapped() async {
+  await Future.delayed(const Duration(seconds: 1));
 
-    if (!mounted) return;
-    Navigator.of(context).pop();
-  }
+  if (!context.mounted) return;
+  Navigator.of(context).pop();
 }
 ```
 ''';
 
-class UseBuildContextSynchronously extends LintRule implements NodeLintRule {
+class UseBuildContextSynchronously extends LintRule {
+  static const LintCode code = LintCode('use_build_context_synchronously',
+      "Don't use 'BuildContext's across async gaps.",
+      correctionMessage:
+          "Try rewriting the code to not reference the 'BuildContext'.");
+
   /// Flag to short-circuit `inTestDir` checking when running tests.
   final bool inTestMode;
 
@@ -66,6 +67,9 @@
             maturity: Maturity.experimental);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var unit = context.currentUnit.unit;
@@ -74,6 +78,7 @@
       registry.addMethodInvocation(this, visitor);
       registry.addInstanceCreationExpression(this, visitor);
       registry.addFunctionExpressionInvocation(this, visitor);
+      registry.addPrefixedIdentifier(this, visitor);
     }
   }
 }
@@ -85,6 +90,17 @@
   void visitAwaitExpression(AwaitExpression node) {
     hasAwait = true;
   }
+
+  @override
+  visitBlockFunctionBody(BlockFunctionBody node) {
+    // Stop visiting if it's a function body block.
+    // Awaits inside it shouldn't matter
+  }
+
+  @override
+  visitExpressionFunctionBody(ExpressionFunctionBody node) {
+    // Stopping following the same logic as function body blocks
+  }
 }
 
 class _Visitor extends SimpleAstVisitor {
@@ -94,6 +110,12 @@
 
   bool accessesContext(ArgumentList argumentList) {
     for (var argument in argumentList.arguments) {
+      if (argument is NamedExpression) {
+        argument = argument.expression;
+      }
+      if (argument is PropertyAccess) {
+        argument = argument.propertyName;
+      }
       if (argument is Identifier) {
         var element = argument.staticElement;
         if (element == null) {
@@ -149,6 +171,10 @@
           return;
         }
       } else if (parent is IfStatement) {
+        if (parent.hasAsyncInCondition) {
+          rule.reportLint(node);
+        }
+
         // if (mounted) { ... do ... }
         if (isMountedCheck(parent, positiveCheck: true)) {
           return;
@@ -161,6 +187,7 @@
 
   bool isAsync(Statement statement) {
     if (statement is IfStatement) {
+      if (statement.hasAsyncInCondition) return true;
       if (terminatesControl(statement.thenStatement)) {
         var elseStatement = statement.elseStatement;
         if (elseStatement == null || terminatesControl(elseStatement)) {
@@ -183,7 +210,7 @@
 
       Expression check;
       if (condition is PrefixExpression) {
-        if (positiveCheck && condition.operator.type == TokenType.BANG) {
+        if (positiveCheck && condition.isNot) {
           return false;
         }
         check = condition.operand;
@@ -191,29 +218,59 @@
         check = condition;
       }
 
-      // stateContext.mounted => mounted
-      if (check is PrefixedIdentifier) {
-        check = check.identifier;
-      }
-      if (check is SimpleIdentifier) {
-        if (check.name == 'mounted') {
-          // In the positive case it's sufficient to know we're in a positively
-          // guarded block.
+      bool checksMounted(Expression check) {
+        if (check is BinaryExpression) {
+          // (condition && context.mounted)
           if (positiveCheck) {
-            return true;
+            if (check.isAnd) {
+              return checksMounted(check.leftOperand) ||
+                  checksMounted(check.rightOperand);
+            }
+          } else {
+            // (condition || !mounted)
+            if (check.isOr) {
+              return checksMounted(check.leftOperand) ||
+                  checksMounted(check.rightOperand);
+            }
           }
-          var then = statement.thenStatement;
-          return terminatesControl(then);
         }
+
+        // stateContext.mounted => mounted
+        if (check is PrefixedIdentifier) {
+          // ignore: parameter_assignments
+          check = check.identifier;
+        }
+        if (check is SimpleIdentifier) {
+          return check.name == 'mounted';
+        }
+        if (check is PrefixExpression) {
+          // (condition || !mounted)
+          if (!positiveCheck && check.isNot) {
+            return checksMounted(check.operand);
+          }
+        }
+
+        return false;
+      }
+
+      if (checksMounted(check)) {
+        // In the positive case it's sufficient to know we're in a positively
+        // guarded block.
+        if (positiveCheck) {
+          return true;
+        }
+        var then = statement.thenStatement;
+        return terminatesControl(then);
       }
     } else if (statement is TryStatement) {
       var statements = statement.finallyBlock?.statements;
-      if (statements != null) {
-        for (var i = statements.length - 1; i >= 0; i--) {
-          var s = statements[i];
-          if (isMountedCheck(s)) {
-            return true;
-          }
+      if (statements == null) {
+        return false;
+      }
+      for (var i = statements.length - 1; i >= 0; i--) {
+        var s = statements[i];
+        if (isMountedCheck(s)) {
+          return true;
         }
       }
     }
@@ -250,4 +307,29 @@
       check(node);
     }
   }
+
+  @override
+  visitPrefixedIdentifier(PrefixedIdentifier node) {
+    // Getter access.
+    if (isBuildContext(node.prefix.staticType, skipNullable: true)) {
+      check(node);
+    }
+  }
+}
+
+extension on PrefixExpression {
+  bool get isNot => operator.type == TokenType.BANG;
+}
+
+extension on BinaryExpression {
+  bool get isAnd => operator.type == TokenType.AMPERSAND_AMPERSAND;
+  bool get isOr => operator.type == TokenType.BAR_BAR;
+}
+
+extension on IfStatement {
+  bool get hasAsyncInCondition {
+    var visitor = _AwaitVisitor();
+    condition.accept(visitor);
+    return visitor.hasAwait;
+  }
 }
diff --git a/lib/src/rules/use_colored_box.dart b/lib/src/rules/use_colored_box.dart
new file mode 100644
index 0000000..3b4812b
--- /dev/null
+++ b/lib/src/rules/use_colored_box.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
+
+import '../analyzer.dart';
+import '../util/flutter_utils.dart';
+
+const _desc = r'Use `ColoredBox`.';
+
+const _details = r'''
+**DO** use `ColoredBox` when `Container` has only a `Color`.
+
+A `Container` is a heavier Widget than a `ColoredBox`, and as bonus,
+`ColoredBox` has a `const` constructor.
+
+**BAD:**
+```dart
+Widget buildArea() {
+  return Container(
+    color: Colors.blue,
+    child: const Text('hello'),
+  );
+}
+```
+
+**GOOD:**
+```dart
+Widget buildArea() {
+  return const ColoredBox(
+    color: Colors.blue,
+    child: Text('hello'),
+  );
+}
+```
+''';
+
+class UseColoredBox extends LintRule {
+  UseColoredBox()
+      : super(
+            name: 'use_colored_box',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+
+    registry.addInstanceCreationExpression(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (!isExactWidgetTypeContainer(node.staticType)) {
+      return;
+    }
+
+    var data = _ArgumentData(node.argumentList);
+
+    if (data.additionalArgumentsFound || data.positionalArgumentsFound) {
+      return;
+    }
+
+    if (data.hasChild && data.hasColor) {
+      rule.reportLint(node.constructorName);
+    }
+  }
+}
+
+class _ArgumentData {
+  var positionalArgumentsFound = false;
+  var additionalArgumentsFound = false;
+  var hasColor = false;
+  var hasChild = false;
+
+  _ArgumentData(ArgumentList node) {
+    for (var argument in node.arguments) {
+      if (argument is! NamedExpression) {
+        positionalArgumentsFound = true;
+        return;
+      }
+      var label = argument.name.label;
+      if (label.name == 'color' &&
+          argument.staticType?.nullabilitySuffix !=
+              NullabilitySuffix.question) {
+        hasColor = true;
+      } else if (label.name == 'child') {
+        hasChild = true;
+      } else if (label.name == 'key') {
+        // Ignore key
+      } else {
+        additionalArgumentsFound = true;
+      }
+    }
+  }
+}
diff --git a/lib/src/rules/use_decorated_box.dart b/lib/src/rules/use_decorated_box.dart
new file mode 100644
index 0000000..183e6c5
--- /dev/null
+++ b/lib/src/rules/use_decorated_box.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+import '../util/flutter_utils.dart';
+
+const _desc = r'Use `DecoratedBox`.';
+
+const _details = r'''
+**DO** use `DecoratedBox` when `Container` has only a `Decoration`.
+
+A `Container` is a heavier Widget than a `DecoratedBox`, and as bonus,
+`DecoratedBox` has a `const` constructor.
+
+**BAD:**
+```dart
+Widget buildArea() {
+  return Container(
+    decoration: const BoxDecoration(
+      color: Colors.blue,
+      borderRadius: BorderRadius.all(
+        Radius.circular(5),
+      ),
+    ),
+    child: const Text('...'),
+  );
+}
+```
+
+**GOOD:**
+```dart
+Widget buildArea() {
+  return const DecoratedBox(
+    decoration: BoxDecoration(
+      color: Colors.blue,
+      borderRadius: BorderRadius.all(
+        Radius.circular(5),
+      ),
+    ),
+    child: Text('...'),
+  );
+}
+```
+''';
+
+class UseDecoratedBox extends LintRule {
+  UseDecoratedBox()
+      : super(
+            name: 'use_decorated_box',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+
+    registry.addInstanceCreationExpression(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (!isExactWidgetTypeContainer(node.staticType)) {
+      return;
+    }
+
+    var data = _ArgumentData(node.argumentList);
+
+    if (data.additionalArgumentsFound || data.positionalArgumentsFound) {
+      return;
+    }
+
+    if (data.hasChild && data.hasDecoration) {
+      rule.reportLint(node.constructorName);
+    }
+  }
+}
+
+class _ArgumentData {
+  var positionalArgumentsFound = false;
+  var additionalArgumentsFound = false;
+  var hasDecoration = false;
+  var hasChild = false;
+
+  _ArgumentData(ArgumentList node) {
+    for (var argument in node.arguments) {
+      if (argument is! NamedExpression) {
+        positionalArgumentsFound = true;
+        return;
+      }
+      var label = argument.name.label;
+      if (label.name == 'decoration') {
+        hasDecoration = true;
+      } else if (label.name == 'child') {
+        hasChild = true;
+      } else if (label.name == 'key') {
+        // Ignore key
+      } else {
+        additionalArgumentsFound = true;
+      }
+    }
+  }
+}
diff --git a/lib/src/rules/use_enums.dart b/lib/src/rules/use_enums.dart
new file mode 100644
index 0000000..9667967
--- /dev/null
+++ b/lib/src/rules/use_enums.dart
@@ -0,0 +1,245 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+
+import '../analyzer.dart';
+import '../ast.dart';
+
+const _desc = r'Use enums rather than classes that behave like enums.';
+
+// todo(pq): add a link to any docs describing enhanced-enums once published
+
+const _details = r'''
+Classes that look like enumerations should be declared as `enum`s.
+
+**DO** use enums where appropriate.
+
+Candidates for enums are classes that:
+  * are concrete,
+  * are private or have only private generative constructors,
+  * have two or more static const fields with the same type as the class,
+  * have generative constructors that are only invoked at the top-level of the
+    initialization expression of these static fields,
+  * do not define `hashCode`, `==`, `values` or `index`,
+  * do not extend any class other than Object, and
+  * have no subclasses declared in the defining library.
+
+**BAD:**
+```dart
+class LogPriority {
+  static const error = LogPriority._(1, 'Error');
+  static const warning = LogPriority._(2, 'Warning');
+  static const log = LogPriority._unknown('Log');
+
+  final String prefix;
+  final int priority;
+  const LogPriority._(this.priority, this.prefix);
+  const LogPriority._unknown(String prefix) : this._(-1, prefix);
+}
+```
+
+**GOOD:**
+```dart
+enum LogPriority {
+  error(1, 'Error'),
+  warning(2, 'Warning'),
+  log.unknown('Log');
+
+  final String prefix;
+  final int priority;
+  const LogPriority(this.priority, this.prefix);
+  const LogPriority.unknown(String prefix) : this(-1, prefix);
+}
+```
+''';
+
+class UseEnums extends LintRule {
+  UseEnums()
+      : super(
+            name: 'use_enums',
+            description: _desc,
+            details: _details,
+            maturity: Maturity.experimental,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    if (!context.isEnabled(Feature.enhanced_enums)) return;
+
+    var visitor = _Visitor(this, context);
+    registry.addClassDeclaration(this, visitor);
+  }
+}
+
+/// A superclass for the [_EnumVisitor] and [_NonEnumVisitor].
+class _BaseVisitor extends RecursiveAstVisitor<void> {
+  /// The element representing the enum declaration that's being visited.
+  final ClassElement classElement;
+
+  _BaseVisitor(this.classElement);
+
+  /// Return `true` if the given [node] is an invocation of a generative
+  /// constructor from the class being converted.
+  bool invokesGenerativeConstructor(InstanceCreationExpression node) {
+    var constructorElement = node.constructorName.staticElement;
+    return constructorElement != null &&
+        !constructorElement.isFactory &&
+        constructorElement.enclosingElement == classElement;
+  }
+}
+
+/// A visitor used to visit the class being linted. This visitor throws an
+/// exception if a constructor for the class is invoked anywhere other than the
+/// top-level expression of an initializer for one of the fields being converted.
+///
+/// Note: copied and modified from server.
+class _EnumVisitor extends _BaseVisitor {
+  /// A flag indicating whether we are currently visiting the children of a
+  /// field declaration that will be converted to be a constant.
+  bool inConstantDeclaration = false;
+
+  List<VariableDeclaration> variableDeclarations;
+
+  _EnumVisitor(super.classElement, this.variableDeclarations);
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (!inConstantDeclaration) {
+      if (invokesGenerativeConstructor(node)) {
+        throw _InvalidEnumException();
+      }
+    }
+    inConstantDeclaration = false;
+    super.visitInstanceCreationExpression(node);
+  }
+
+  @override
+  void visitVariableDeclaration(VariableDeclaration node) {
+    if (variableDeclarations.contains(node)) {
+      inConstantDeclaration = true;
+    }
+    super.visitVariableDeclaration(node);
+    inConstantDeclaration = false;
+  }
+}
+
+/// Note: copied and modified from server.
+class _InvalidEnumException implements Exception {}
+
+/// A visitor that visits everything in the library other than the class being
+/// linted. This visitor throws an exception if
+/// - there is a subclass of the class, or
+/// - there is an invocation of one of the constructors of the class.
+///
+/// Note: copied and modified from server.
+class _NonEnumVisitor extends _BaseVisitor {
+  /// Initialize a newly created visitor to visit everything except the class
+  /// declaration corresponding to the given [classElement].
+  _NonEnumVisitor(super.classElement);
+
+  @override
+  void visitClassDeclaration(ClassDeclaration node) {
+    var element = node.declaredElement;
+    if (element == null) {
+      throw _InvalidEnumException();
+    }
+    if (element != classElement) {
+      if (element.supertype?.element == classElement) {
+        throw _InvalidEnumException();
+      } else if (element.interfaces
+          .map((e) => e.element)
+          .contains(classElement)) {
+        throw _InvalidEnumException();
+      } else if (element.mixins.map((e) => e.element).contains(classElement)) {
+        // This case won't occur unless there's an error in the source code, but
+        // it's easier to check for the condition than it is to check for the
+        // diagnostic.
+        throw _InvalidEnumException();
+      }
+      super.visitClassDeclaration(node);
+    }
+  }
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    if (invokesGenerativeConstructor(node)) {
+      throw _InvalidEnumException();
+    }
+    super.visitInstanceCreationExpression(node);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
+  final LinterContext context;
+
+  _Visitor(this.rule, this.context);
+
+  @override
+  visitClassDeclaration(ClassDeclaration node) {
+    if (node.abstractKeyword != null) return;
+    var classElement = node.declaredElement;
+    if (classElement == null) return;
+
+    // Enums can only extend Object.
+    if (classElement.supertype?.isDartCoreObject == false) {
+      return;
+    }
+
+    var candidateConstants = <VariableDeclaration>[];
+
+    for (var member in node.members) {
+      if (isHashCode(member)) return;
+      if (isIndex(member)) return;
+      if (isEquals(member)) return;
+      if (isValues(member)) return;
+
+      if (member is FieldDeclaration) {
+        if (!member.isStatic) continue;
+        for (var field in member.fields.variables) {
+          var fieldElement = field.declaredElement;
+          if (fieldElement is! FieldElement) continue;
+          if (field.isSynthetic || !field.isConst) continue;
+          var initializer = field.initializer;
+          if (initializer is! InstanceCreationExpression) continue;
+
+          var constructorElement = initializer.constructorName.staticElement;
+          if (constructorElement == null) continue;
+          if (constructorElement.isFactory) continue;
+          if (constructorElement.enclosingElement != classElement) continue;
+
+          if (fieldElement.computeConstantValue() == null) continue;
+
+          candidateConstants.add(field);
+        }
+      }
+      if (member is ConstructorDeclaration) {
+        var constructor = member.declaredElement;
+        if (constructor == null) return;
+        if (!constructor.isFactory && !constructor.isConst) return;
+        var name = member.name?.lexeme;
+        if (classElement.isPublic &&
+            (name == null || !Identifier.isPrivateName(name))) {
+          return;
+        }
+      }
+    }
+
+    if (candidateConstants.length < 2) return;
+
+    try {
+      node.accept(_EnumVisitor(classElement, candidateConstants));
+      node.root.accept(_NonEnumVisitor(classElement));
+    } on _InvalidEnumException {
+      return;
+    }
+
+    rule.reportLintForToken(node.name);
+  }
+}
diff --git a/lib/src/rules/use_full_hex_values_for_flutter_colors.dart b/lib/src/rules/use_full_hex_values_for_flutter_colors.dart
index c300bbd..a64f30c 100644
--- a/lib/src/rules/use_full_hex_values_for_flutter_colors.dart
+++ b/lib/src/rules/use_full_hex_values_for_flutter_colors.dart
@@ -6,14 +6,13 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc =
     r'Prefer an 8-digit hexadecimal integer(0xFFFFFFFF) to instantiate Color.';
 
 const _details = r'''
-
-Prefer an 8-digit hexadecimal integer(0xFFFFFFFF) to instantiate Color. Colors
+**PREFER** an 8-digit hexadecimal integer(0xFFFFFFFF) to instantiate Color. Colors
 have four 8-bit channels, which adds up to 32 bits, so Colors are described
 using a 32 bit integer.
 
@@ -30,8 +29,14 @@
 
 ''';
 
-class UseFullHexValuesForFlutterColors extends LintRule
-    implements NodeLintRule {
+class UseFullHexValuesForFlutterColors extends LintRule {
+  static const LintCode code = LintCode(
+      'use_full_hex_values_for_flutter_colors',
+      "Instances of 'Color' should be created using an 8-digit hexadecimal "
+          "integer (such as '0xFFFFFFFF').",
+      correctionMessage:
+          "Try using an 8-digit hexadecimal integer to create the 'Color'.");
+
   UseFullHexValuesForFlutterColors()
       : super(
             name: 'use_full_hex_values_for_flutter_colors',
@@ -40,6 +45,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -55,8 +63,9 @@
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     var element = node.constructorName.staticElement;
-    if (DartTypeUtilities.isConstructorElement(element,
-        uriStr: 'dart.ui', className: 'Color', constructorName: '')) {
+    if (element != null &&
+        element.isSameAs(
+            uri: 'dart.ui', className: 'Color', constructorName: '')) {
       var arguments = node.argumentList.arguments;
       if (arguments.isNotEmpty) {
         var argument = arguments.first;
diff --git a/lib/src/rules/use_function_type_syntax_for_parameters.dart b/lib/src/rules/use_function_type_syntax_for_parameters.dart
index 0f978a3..c1679bb 100644
--- a/lib/src/rules/use_function_type_syntax_for_parameters.dart
+++ b/lib/src/rules/use_function_type_syntax_for_parameters.dart
@@ -10,7 +10,6 @@
 const _desc = r'Use generic function type syntax for parameters.';
 
 const _details = r'''
-
 Use generic function type syntax for parameters.
 
 **BAD:**
@@ -25,8 +24,12 @@
 
 ''';
 
-class UseFunctionTypeSyntaxForParameters extends LintRule
-    implements NodeLintRule {
+class UseFunctionTypeSyntaxForParameters extends LintRule {
+  static const LintCode code = LintCode(
+      'use_function_type_syntax_for_parameters',
+      "Use the generic function type syntax to declare the parameter '{0}'.",
+      correctionMessage: 'Try using the generic function type syntax.');
+
   UseFunctionTypeSyntaxForParameters()
       : super(
             name: 'use_function_type_syntax_for_parameters',
@@ -35,6 +38,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -49,6 +55,6 @@
 
   @override
   void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
-    rule.reportLint(node);
+    rule.reportLint(node, arguments: [node.name.lexeme]);
   }
 }
diff --git a/lib/src/rules/use_if_null_to_convert_nulls_to_bools.dart b/lib/src/rules/use_if_null_to_convert_nulls_to_bools.dart
index b97918b..41504f6 100644
--- a/lib/src/rules/use_if_null_to_convert_nulls_to_bools.dart
+++ b/lib/src/rules/use_if_null_to_convert_nulls_to_bools.dart
@@ -13,6 +13,7 @@
 const _desc = r'Use if-null operators to convert nulls to bools.';
 
 const _details = r'''
+From [Effective Dart](https://dart.dev/guides/language/effective-dart/usage#prefer-using--to-convert-null-to-a-boolean-value):
 
 Use if-null operators to convert nulls to bools.
 
@@ -34,7 +35,7 @@
 
 ''';
 
-class UseIfNullToConvertNullsToBools extends LintRule implements NodeLintRule {
+class UseIfNullToConvertNullsToBools extends LintRule {
   UseIfNullToConvertNullsToBools()
       : super(
           name: 'use_if_null_to_convert_nulls_to_bools',
diff --git a/lib/src/rules/use_is_even_rather_than_modulo.dart b/lib/src/rules/use_is_even_rather_than_modulo.dart
index 0ff70bc..8e3c854 100644
--- a/lib/src/rules/use_is_even_rather_than_modulo.dart
+++ b/lib/src/rules/use_is_even_rather_than_modulo.dart
@@ -12,7 +12,6 @@
     r'Prefer intValue.isOdd/isEven instead of checking the result of % 2.';
 
 const _details = r'''
-
 **PREFER** the use of intValue.isOdd/isEven to check for evenness.
 
 **BAD:**
@@ -29,7 +28,7 @@
 
 ''';
 
-class UseIsEvenRatherThanModuloCheck extends LintRule implements NodeLintRule {
+class UseIsEvenRatherThanModuloCheck extends LintRule {
   UseIsEvenRatherThanModuloCheck()
       : super(
             name: 'use_is_even_rather_than_modulo',
diff --git a/lib/src/rules/use_key_in_widget_constructors.dart b/lib/src/rules/use_key_in_widget_constructors.dart
index e8afe58..1243772 100644
--- a/lib/src/rules/use_key_in_widget_constructors.dart
+++ b/lib/src/rules/use_key_in_widget_constructors.dart
@@ -8,7 +8,7 @@
 import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 import '../util/flutter_utils.dart';
 
 const _desc = r'Use key in widget constructors.';
@@ -28,12 +28,16 @@
 **GOOD:**
 ```dart
 class MyPublicWidget extends StatelessWidget {
-  MyPublicWidget({Key? key}) : super(key: key);
+  MyPublicWidget({super.key});
 }
 ```
 ''';
 
-class UseKeyInWidgetConstructors extends LintRule implements NodeLintRule {
+class UseKeyInWidgetConstructors extends LintRule {
+  static const LintCode code = LintCode('use_key_in_widget_constructors',
+      "Constructors for public widgets should have a named 'key' parameter.",
+      correctionMessage: 'Try adding a named parameter to the constructor.');
+
   UseKeyInWidgetConstructors()
       : super(
             name: 'use_key_in_widget_constructors',
@@ -42,6 +46,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -62,7 +69,7 @@
         classElement.isPublic &&
         hasWidgetAsAscendant(classElement) &&
         classElement.constructors.where((e) => !e.isSynthetic).isEmpty) {
-      rule.reportLint(node.name);
+      rule.reportLintForToken(node.name);
     }
     super.visitClassDeclaration(node);
   }
@@ -77,8 +84,10 @@
     if (constructorElement.isPublic &&
         !constructorElement.isFactory &&
         classElement.isPublic &&
+        classElement is ClassElement &&
         hasWidgetAsAscendant(classElement) &&
         !isExactWidget(classElement) &&
+        !_hasKeySuperParameterInitializerArg(node) &&
         !node.initializers.any((initializer) {
           if (initializer is SuperConstructorInvocation) {
             var staticElement = initializer.staticElement;
@@ -93,17 +102,28 @@
           }
           return false;
         })) {
-      rule.reportLintForToken(node.firstTokenAfterCommentAndMetadata);
+      var errorNode = node.name ?? node.returnType;
+      rule.reportLintForOffset(errorNode.offset, errorNode.length);
     }
     super.visitConstructorDeclaration(node);
   }
 
-  bool _defineKeyParameter(ConstructorElement element) =>
-      element.parameters.any((e) => e.name == 'key' && _isKeyType(e.type));
-
   bool _defineKeyArgument(ArgumentList argumentList) => argumentList.arguments
       .any((a) => a.staticParameterElement?.name == 'key');
 
-  bool _isKeyType(DartType type) =>
-      DartTypeUtilities.implementsInterface(type, 'Key', '');
+  bool _defineKeyParameter(ConstructorElement element) =>
+      element.parameters.any((e) => e.name == 'key' && _isKeyType(e.type));
+
+  bool _hasKeySuperParameterInitializerArg(ConstructorDeclaration node) {
+    for (var parameter in node.parameters.parameters) {
+      var element = parameter.declaredElement;
+      if (element is SuperFormalParameterElement && element.name == 'key') {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool _isKeyType(DartType type) => type.implementsInterface('Key', '');
 }
diff --git a/lib/src/rules/use_late_for_private_fields_and_variables.dart b/lib/src/rules/use_late_for_private_fields_and_variables.dart
index d2e5051..2f46624 100644
--- a/lib/src/rules/use_late_for_private_fields_and_variables.dart
+++ b/lib/src/rules/use_late_for_private_fields_and_variables.dart
@@ -10,15 +10,14 @@
 import 'package:analyzer/error/error.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
-const _desc = r'Use late for private members with non-nullable type.';
+const _desc = r'Use late for private members with a non-nullable type.';
 
 const _details = r'''
-
-Use late for private members with non-nullable types that are always expected to
-be non-null. Thus it's clear that the field is not expected to be `null` and it
-avoids null checks.
+Use `late` for private members with non-nullable types that are always expected
+to be non-null. Thus it's clear that the field is not expected to be `null`
+and it avoids null checks.
 
 **BAD:**
 ```dart
@@ -36,18 +35,26 @@
 }
 ```
 
+**OK:**
+```dart
+int? _i;
+m() {
+  _i?.abs();
+  _i = null;
+}
+```
+
 ''';
 
 bool _isPrivateExtension(AstNode parent) {
   if (parent is! ExtensionDeclaration) {
     return false;
   }
-  var parentName = parent.name?.name;
+  var parentName = parent.name?.lexeme;
   return parentName == null || Identifier.isPrivateName(parentName);
 }
 
-class UseLateForPrivateFieldsAndVariables extends LintRule
-    implements NodeLintRule {
+class UseLateForPrivateFieldsAndVariables extends LintRule {
   UseLateForPrivateFieldsAndVariables()
       : super(
           name: 'use_late_for_private_fields_and_variables',
@@ -65,7 +72,7 @@
   }
 }
 
-class _Visitor extends UnifyingAstVisitor<void> {
+class _Visitor extends RecursiveAstVisitor<void> {
   static final lateables =
       <CompilationUnitElement, List<VariableDeclaration>>{};
 
@@ -78,6 +85,25 @@
   _Visitor(this.rule, this.context);
 
   @override
+  void visitAssignmentExpression(AssignmentExpression node) {
+    var element = node.writeElement?.canonicalElement;
+    if (element != null) {
+      var assignee = node.leftHandSide;
+      var rhsType = node.rightHandSide.staticType;
+      if (assignee is SimpleIdentifier && assignee.inDeclarationContext()) {
+        // This is OK.
+      } else if (node.operator.type == TokenType.EQ &&
+          rhsType != null &&
+          context.typeSystem.isNonNullable(rhsType)) {
+        // This is OK; non-null access.
+      } else {
+        nullableAccess[currentUnit]?.add(element);
+      }
+    }
+    super.visitAssignmentExpression(node);
+  }
+
+  @override
   void visitClassDeclaration(ClassDeclaration node) {
     // See: https://dart.dev/tools/diagnostic-messages#late_final_field_with_const_constructor
     for (var member in node.members) {
@@ -111,7 +137,7 @@
       if (areAllLibraryUnitsVisited) {
         _checkAccess(libraryUnitsInContext);
 
-        // clean up
+        // Clean up.
         for (var unit in libraryUnitsInContext) {
           lateables.remove(unit);
           nullableAccess.remove(unit);
@@ -121,60 +147,56 @@
   }
 
   @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    // See: https://dart.dev/tools/diagnostic-messages#late_final_field_with_const_constructor
+  }
+
+  @override
   void visitFieldDeclaration(FieldDeclaration node) {
-    for (var variable in node.fields.variables) {
-      var parent = node.parent;
-      // see https://github.com/dart-lang/linter/pull/2189#issuecomment-660115569
-      // We could also include public members in private classes but to do that
-      // we'd need to ensure that there are no instances of either the
-      // enclosing class or any subclass of the enclosing class that are ever
-      // accessible outside this library.
-      if (parent != null &&
-          (Identifier.isPrivateName(variable.name.name) ||
-              _isPrivateExtension(parent))) {
-        _visit(variable);
+    var parent = node.parent;
+    if (parent != null) {
+      var parentIsPrivateExtension = _isPrivateExtension(parent);
+      for (var variable in node.fields.variables) {
+        // See
+        // https://github.com/dart-lang/linter/pull/2189#issuecomment-660115569.
+        // We could also include public members in private classes but to do
+        // that we'd need to ensure that there are no instances of either the
+        // enclosing class or any subclass of the enclosing class that are ever
+        // accessible outside this library.
+        if (parentIsPrivateExtension ||
+            Identifier.isPrivateName(variable.name.lexeme)) {
+          _visit(variable);
+        }
       }
     }
     super.visitFieldDeclaration(node);
   }
 
   @override
-  void visitNode(AstNode node) {
-    var parent = node.parent;
+  void visitPrefixedIdentifier(PrefixedIdentifier node) {
+    var element = node.staticElement?.canonicalElement;
+    _visitIdentifierOrPropertyAccess(node, element);
+    super.visitPrefixedIdentifier(node);
+  }
 
-    Element? element;
-    if (parent is AssignmentExpression && parent.leftHandSide == node) {
-      element = DartTypeUtilities.getCanonicalElement(parent.writeElement);
-    } else {
-      element = DartTypeUtilities.getCanonicalElementFromIdentifier(node);
-    }
+  @override
+  void visitPropertyAccess(PropertyAccess node) {
+    var element = node.propertyName.staticElement?.canonicalElement;
+    _visitIdentifierOrPropertyAccess(node, element);
+    super.visitPropertyAccess(node);
+  }
 
-    if (element != null) {
-      if (parent is Expression) {
-        parent = parent.unParenthesized;
-      }
-      if (node is SimpleIdentifier && node.inDeclarationContext()) {
-        // ok
-      } else if (parent is PostfixExpression &&
-          parent.operand == node &&
-          parent.operator.type == TokenType.BANG) {
-        // ok non-null access
-      } else if (parent is AssignmentExpression &&
-          parent.operator.type == TokenType.EQ &&
-          DartTypeUtilities.isNonNullable(
-              context, parent.rightHandSide.staticType)) {
-        // ok non-null access
-      } else {
-        nullableAccess[currentUnit]?.add(element);
-      }
-    }
-    super.visitNode(node);
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    var element = node.staticElement?.canonicalElement;
+    _visitIdentifierOrPropertyAccess(node, element);
+    super.visitSimpleIdentifier(node);
   }
 
   @override
   void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
     for (var variable in node.variables.variables) {
-      if (Identifier.isPrivateName(variable.name.name)) {
+      if (Identifier.isPrivateName(variable.name.lexeme)) {
         _visit(variable);
       }
     }
@@ -208,4 +230,26 @@
     }
     lateables[currentUnit]?.add(variable);
   }
+
+  /// Checks whether [expression], which must be an [Identifier] or
+  /// [PropertyAccess], and its [canonicalElement], represent a nullable access.
+  void _visitIdentifierOrPropertyAccess(
+      Expression expression, Element? canonicalElement) {
+    assert(expression is Identifier || expression is PropertyAccess);
+    if (canonicalElement != null) {
+      var parent = expression.parent;
+      if (parent is Expression) {
+        parent = parent.unParenthesized;
+      }
+      if (expression is SimpleIdentifier && expression.inDeclarationContext()) {
+        // This is OK.
+      } else if (parent is PostfixExpression &&
+          parent.operand == expression &&
+          parent.operator.type == TokenType.BANG) {
+        // This is OK; non-null access.
+      } else {
+        nullableAccess[currentUnit]?.add(canonicalElement);
+      }
+    }
+  }
 }
diff --git a/lib/src/rules/use_named_constants.dart b/lib/src/rules/use_named_constants.dart
index 1193b93..e15ebbf 100644
--- a/lib/src/rules/use_named_constants.dart
+++ b/lib/src/rules/use_named_constants.dart
@@ -5,13 +5,13 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
 
 const _desc = r'Use predefined named constants.';
 
 const _details = r'''
-
 Where possible, use already defined const values.
 
 **BAD:**
@@ -27,7 +27,7 @@
 ''';
 const lintName = 'use_named_constants';
 
-class UseNamedConstants extends LintRule implements NodeLintRule {
+class UseNamedConstants extends LintRule {
   UseNamedConstants()
       : super(
           name: lintName,
@@ -53,7 +53,9 @@
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     if (node.isConst) {
-      var element = node.staticType?.element;
+      var type = node.staticType;
+      if (type is! InterfaceType) return;
+      var element = type.element;
       if (element is ClassElement) {
         var nodeField =
             node.thisOrAncestorOfType<VariableDeclaration>()?.declaredElement;
@@ -67,6 +69,7 @@
         if (nodeField?.enclosingElement == element) return;
 
         var library = (node.root as CompilationUnit).declaredElement?.library;
+        if (library == null) return;
         var value = context.evaluateConstant(node).value;
         for (var field
             in element.fields.where((e) => e.isStatic && e.isConst)) {
@@ -76,7 +79,8 @@
                 arguments: ['${element.name}.${field.name}'],
                 errorCode: const LintCode(lintName,
                     "The constant '{0}' should be referenced instead of duplicating its value.",
-                    correction: "Try using the predefined constant '{0}'."));
+                    correctionMessage:
+                        "Try using the predefined constant '{0}'."));
             return;
           }
         }
diff --git a/lib/src/rules/use_raw_strings.dart b/lib/src/rules/use_raw_strings.dart
index 318d957..a522f28 100644
--- a/lib/src/rules/use_raw_strings.dart
+++ b/lib/src/rules/use_raw_strings.dart
@@ -10,7 +10,6 @@
 const _desc = r'Use raw string to avoid escapes.';
 
 const _details = r'''
-
 A raw string can be used to avoid escaping only backslashes and dollars.
 
 **BAD:**
@@ -25,7 +24,7 @@
 
 ''';
 
-class UseRawStrings extends LintRule implements NodeLintRule {
+class UseRawStrings extends LintRule {
   UseRawStrings()
       : super(
             name: 'use_raw_strings',
diff --git a/lib/src/rules/use_rethrow_when_possible.dart b/lib/src/rules/use_rethrow_when_possible.dart
index 4037bdd..4e78a27 100644
--- a/lib/src/rules/use_rethrow_when_possible.dart
+++ b/lib/src/rules/use_rethrow_when_possible.dart
@@ -6,12 +6,11 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Use rethrow to rethrow a caught exception.';
 
 const _details = r'''
-
 **DO** use rethrow to rethrow a caught exception.
 
 As Dart provides rethrow as a feature, it should be used to improve terseness
@@ -39,7 +38,11 @@
 
 ''';
 
-class UseRethrowWhenPossible extends LintRule implements NodeLintRule {
+class UseRethrowWhenPossible extends LintRule {
+  static const LintCode code = LintCode('use_rethrow_when_possible',
+      "Use 'rethrow' to rethrow a caught exception.",
+      correctionMessage: "Try replacing the 'throw' with a 'rethrow'.");
+
   UseRethrowWhenPossible()
       : super(
             name: 'use_rethrow_when_possible',
@@ -48,6 +51,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -62,13 +68,13 @@
 
   @override
   void visitThrowExpression(ThrowExpression node) {
-    var element =
-        DartTypeUtilities.getCanonicalElementFromIdentifier(node.expression);
+    if (node.parent is Expression || node.parent is ArgumentList) return;
+
+    var element = node.expression.canonicalElement;
     if (element != null) {
       var catchClause = node.thisOrAncestorOfType<CatchClause>();
       var exceptionParameter =
-          DartTypeUtilities.getCanonicalElementFromIdentifier(
-              catchClause?.exceptionParameter);
+          catchClause?.exceptionParameter?.declaredElement?.canonicalElement;
       if (element == exceptionParameter) {
         rule.reportLint(node);
       }
diff --git a/lib/src/rules/use_setters_to_change_properties.dart b/lib/src/rules/use_setters_to_change_properties.dart
index ef865e0..d1fd18e 100644
--- a/lib/src/rules/use_setters_to_change_properties.dart
+++ b/lib/src/rules/use_setters_to_change_properties.dart
@@ -8,13 +8,12 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc =
     r'Use a setter for operations that conceptually change a property.';
 
 const _details = r'''
-
 **DO** use a setter for operations that conceptually change a property.
 
 **BAD:**
@@ -31,7 +30,7 @@
 
 ''';
 
-class UseSettersToChangeAProperty extends LintRule implements NodeLintRule {
+class UseSettersToChangeAProperty extends LintRule {
   UseSettersToChangeAProperty()
       : super(
             name: 'use_setters_to_change_properties',
@@ -56,7 +55,7 @@
   void visitMethodDeclaration(MethodDeclaration node) {
     if (node.isSetter ||
         node.isGetter ||
-        DartTypeUtilities.overridesMethod(node) ||
+        node.isOverride ||
         node.parameters?.parameters.length != 1 ||
         node.returnType?.type?.isVoid != true) {
       return;
@@ -65,13 +64,11 @@
     void checkExpression(Expression expression) {
       if (expression is AssignmentExpression &&
           expression.operator.type == TokenType.EQ) {
-        var leftOperand =
-            DartTypeUtilities.getCanonicalElement(expression.writeElement);
-        var rightOperand = DartTypeUtilities.getCanonicalElementFromIdentifier(
-            expression.rightHandSide);
+        var leftOperand = expression.writeElement?.canonicalElement;
+        var rightOperand = expression.rightHandSide.canonicalElement;
         var parameterElement = node.declaredElement?.parameters.first;
         if (rightOperand == parameterElement && leftOperand is FieldElement) {
-          rule.reportLint(node.name);
+          rule.reportLintForToken(node.name);
         }
       }
     }
diff --git a/lib/src/rules/use_string_buffers.dart b/lib/src/rules/use_string_buffers.dart
index 1de312b..5e5f534 100644
--- a/lib/src/rules/use_string_buffers.dart
+++ b/lib/src/rules/use_string_buffers.dart
@@ -6,14 +6,14 @@
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc = r'Use string buffers to compose strings.';
 
 const _details = r'''
-
 **DO** use string buffers to compose strings.
 
 In most cases, using a string buffer is preferred for composing strings due to
@@ -52,7 +52,7 @@
 /// step it creates an auxiliary String that takes O(amount of chars) to be
 /// computed, in otherwise using a StringBuffer the order is reduced to O(~N)
 /// so the bad case is N times slower than the good case.
-class UseStringBuffers extends LintRule implements NodeLintRule {
+class UseStringBuffers extends LintRule {
   UseStringBuffers()
       : super(
             name: 'use_string_buffers',
@@ -121,11 +121,12 @@
         node.operator.type != TokenType.EQ) return;
 
     var left = node.leftHandSide;
+    var writeType = node.writeType;
     if (left is SimpleIdentifier &&
-        DartTypeUtilities.isClass(node.writeType, 'String', 'dart.core')) {
+        writeType is InterfaceType &&
+        writeType.isDartCoreString) {
       if (node.operator.type == TokenType.PLUS_EQ &&
-          !localElements.contains(
-              DartTypeUtilities.getCanonicalElement(node.writeElement))) {
+          !localElements.contains(node.writeElement?.canonicalElement)) {
         rule.reportLint(node);
       }
       if (node.operator.type == TokenType.EQ) {
diff --git a/lib/src/rules/use_string_in_part_of_directives.dart b/lib/src/rules/use_string_in_part_of_directives.dart
new file mode 100644
index 0000000..90d45cf
--- /dev/null
+++ b/lib/src/rules/use_string_in_part_of_directives.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Use string in part of directives.';
+
+const _details = r'''
+From [Effective Dart](https://dart.dev/guides/language/effective-dart/usage#do-use-strings-in-part-of-directives):
+
+**DO** use strings in `part of` directives.
+
+**BAD:**
+
+```dart
+part of my_library;
+```
+
+**GOOD:**
+
+```dart
+part of '../../my_library.dart';
+```
+
+''';
+
+class UseStringInPartOfDirectives extends LintRule {
+  UseStringInPartOfDirectives()
+      : super(
+          name: 'use_string_in_part_of_directives',
+          description: _desc,
+          details: _details,
+          group: Group.style,
+        );
+
+  @override
+  void registerNodeProcessors(
+    NodeLintRegistry registry,
+    LinterContext context,
+  ) {
+    var visitor = _Visitor(this);
+    registry.addPartOfDirective(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  _Visitor(this.rule);
+
+  final LintRule rule;
+
+  @override
+  void visitPartOfDirective(PartOfDirective node) {
+    if (node.libraryName != null) {
+      rule.reportLint(node);
+    }
+  }
+}
diff --git a/lib/src/rules/use_super_parameters.dart b/lib/src/rules/use_super_parameters.dart
new file mode 100644
index 0000000..0bd5086
--- /dev/null
+++ b/lib/src/rules/use_super_parameters.dart
@@ -0,0 +1,276 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/utilities/extensions/string.dart'; // ignore: implementation_imports
+
+import '../analyzer.dart';
+
+const _desc = r'Use super-initializer parameters where possible.';
+
+const _details = r'''
+"Forwarding constructor"s, that do nothing except forward parameters to their 
+superclass constructors should take advantage of super-initializer parameters 
+rather than repeating the names of parameters when passing them to the 
+superclass constructors.  This makes the code more concise and easier to read
+and maintain.
+
+**DO** use super-initializer parameters where possible.
+
+**BAD:**
+```dart
+class A {
+  A({int? x, int? y});
+}
+class B extends A {
+  B({int? x, int? y}) : super(x: x, y: y);
+}
+```
+
+**GOOD:**
+```dart
+class A {
+  A({int? x, int? y});
+}
+class B extends A {
+  B({super.x, super.y});
+}
+```
+''';
+
+/// Return a set containing the elements of all of the parameters that are
+/// referenced in the body of the [constructor].
+Set<ParameterElement> _referencedParameters(
+    ConstructorDeclaration constructor) {
+  var collector = _ReferencedParameterCollector();
+  constructor.body.accept(collector);
+  return collector.foundParameters;
+}
+
+class UseSuperParameters extends LintRule {
+  static const LintCode singleParam =
+      LintCode('use_super_parameters', "Convert '{0}' to a super parameter.");
+  static const LintCode multipleParams =
+      LintCode('use_super_parameters', 'Convert {0} to super parameters.');
+
+  UseSuperParameters()
+      : super(
+            name: 'use_super_parameters',
+            description: _desc,
+            details: _details,
+            maturity: Maturity.experimental,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    if (!context.isEnabled(Feature.super_parameters)) return;
+
+    var visitor = _Visitor(this, context);
+    registry.addConstructorDeclaration(this, visitor);
+  }
+}
+
+class _ReferencedParameterCollector extends RecursiveAstVisitor<void> {
+  final Set<ParameterElement> foundParameters = {};
+
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    var element = node.staticElement;
+    if (element is ParameterElement) {
+      foundParameters.add(element);
+    }
+  }
+}
+
+class _Visitor extends SimpleAstVisitor {
+  final LinterContext context;
+  final LintRule rule;
+
+  _Visitor(this.rule, this.context);
+
+  void check(
+      ConstructorDeclaration node,
+      SuperConstructorInvocation superInvocation,
+      FormalParameterList parameters) {
+    var constructorElement = superInvocation.staticElement;
+    if (constructorElement == null) return;
+
+    // todo(pq): consolidate logic shared w/ server
+    //  (https://github.com/dart-lang/linter/issues/3263)
+
+    var referencedParameters = _referencedParameters(node);
+
+    var identifiers = _checkForConvertiblePositionalParams(
+        constructorElement, superInvocation, parameters, referencedParameters);
+
+    // Bail if there are positional params that can't be converted.
+    if (identifiers == null) return;
+
+    for (var parameter in parameters.parameters) {
+      var parameterElement = parameter.declaredElement;
+      if (parameterElement == null) continue;
+      if (parameterElement is FieldFormalParameterElement) continue;
+      if (parameterElement.isNamed &&
+          !referencedParameters.contains(parameterElement)) {
+        if (_checkNamedParameter(
+            parameter, parameterElement, constructorElement, superInvocation)) {
+          var identifier = parameter.name?.lexeme;
+          if (identifier != null) {
+            identifiers.add(identifier);
+          }
+        }
+      }
+    }
+
+    _reportLint(node, identifiers);
+  }
+
+  @override
+  visitConstructorDeclaration(ConstructorDeclaration node) {
+    for (var initializer in node.initializers.reversed) {
+      if (initializer is SuperConstructorInvocation) {
+        check(node, initializer, node.parameters);
+        return;
+      }
+    }
+  }
+
+  /// Check if all super positional parameters can be converted to use super-
+  /// initializers. Return a list of convertible named parameters or `null` if
+  /// there are parameters that can't be converted since this will short-circuit
+  /// the lint.
+  List<String>? _checkForConvertiblePositionalParams(
+      ConstructorElement constructorElement,
+      SuperConstructorInvocation superInvocation,
+      FormalParameterList parameters,
+      Set<ParameterElement> referencedParameters) {
+    var positionalSuperArgs = <SimpleIdentifier>[];
+    for (var arg in superInvocation.argumentList.arguments) {
+      if (arg is SimpleIdentifier) {
+        positionalSuperArgs.add(arg);
+      } else if (arg is! NamedExpression) {
+        return null;
+      }
+    }
+
+    if (positionalSuperArgs.isEmpty) return [];
+
+    var constructorParams = parameters.parameters;
+    var convertibleConstructorParams = <String>[];
+    var matchedConstructorParamIndex = 0;
+
+    var seenSuperParams = <Element>{};
+
+    // For each super arg, ensure there is a constructor param (in the right
+    // order).
+    for (var i = 0; i < positionalSuperArgs.length; ++i) {
+      var superArg = positionalSuperArgs[i];
+      var superParam = superArg.staticElement;
+      if (superParam is! ParameterElement) return null;
+      if (superParam.isNamed) return null;
+
+      // Check for the case where a super param is used more than once.
+      if (!seenSuperParams.add(superParam)) return null;
+
+      bool match = false;
+      for (var i = 0; i < constructorParams.length && !match; ++i) {
+        var constructorParam = constructorParams[i];
+        if (constructorParam is FieldFormalParameter) return null;
+        if (constructorParam is SuperFormalParameter) return null;
+        var constructorElement = constructorParam.declaredElement;
+        if (constructorElement == null) continue;
+        if (referencedParameters.contains(constructorElement)) return null;
+        if (constructorElement == superParam) {
+          // Compare the types.
+          var superType = superParam.type;
+          var argType = constructorElement.type;
+          if (!context.typeSystem.isSubtypeOf(argType, superType)) {
+            return null;
+          }
+
+          match = true;
+          var identifier = constructorParam.name?.lexeme;
+          if (identifier != null) {
+            convertibleConstructorParams.add(identifier);
+          }
+          // Ensure we're not out of order.
+          if (i < matchedConstructorParamIndex) return null;
+          matchedConstructorParamIndex = i;
+        }
+      }
+    }
+
+    return convertibleConstructorParams;
+  }
+
+  /// Return `true` if the named [parameter] can be converted into a super
+  /// initializing formal parameter.
+  bool _checkNamedParameter(
+      FormalParameter parameter,
+      ParameterElement parameterElement,
+      ConstructorElement superConstructor,
+      SuperConstructorInvocation superInvocation) {
+    var superParameter =
+        _correspondingNamedParameter(superConstructor, parameterElement);
+    if (superParameter == null) return false;
+
+    bool matchingArgument = false;
+    var arguments = superInvocation.argumentList.arguments;
+    for (var argument in arguments) {
+      if (argument is NamedExpression &&
+          argument.name.label.name == parameterElement.name) {
+        var expression = argument.expression;
+        if (expression is SimpleIdentifier &&
+            expression.staticElement == parameterElement) {
+          matchingArgument = true;
+          break;
+        }
+      }
+    }
+    if (!matchingArgument) {
+      // If the parameter isn't being passed to the super constructor, then
+      // don't lint.
+      return false;
+    }
+
+    // Compare the types.
+    var superType = superParameter.type;
+    var thisType = parameterElement.type;
+    if (!context.typeSystem.isAssignableTo(superType, thisType)) {
+      // If the type of the parameter can't be assigned to the super parameter,
+      // then don't lint.
+      return false;
+    }
+
+    return true;
+  }
+
+  ParameterElement? _correspondingNamedParameter(
+      ConstructorElement superConstructor, ParameterElement thisParameter) {
+    for (var superParameter in superConstructor.parameters) {
+      if (superParameter.isNamed && superParameter.name == thisParameter.name) {
+        return superParameter;
+      }
+    }
+    return null;
+  }
+
+  void _reportLint(ConstructorDeclaration node, List<String> identifiers) {
+    if (identifiers.isEmpty) return;
+    var target = node.name ?? node.returnType;
+    if (identifiers.length > 1) {
+      var msg = identifiers.quotedAndCommaSeparatedWithAnd;
+      rule.reportLintForOffset(target.offset, target.length,
+          errorCode: UseSuperParameters.multipleParams, arguments: [msg]);
+    } else {
+      rule.reportLintForOffset(target.offset, target.length,
+          errorCode: UseSuperParameters.singleParam,
+          arguments: [identifiers.first]);
+    }
+  }
+}
diff --git a/lib/src/rules/use_test_throws_matchers.dart b/lib/src/rules/use_test_throws_matchers.dart
index 8031311..259863e 100644
--- a/lib/src/rules/use_test_throws_matchers.dart
+++ b/lib/src/rules/use_test_throws_matchers.dart
@@ -11,7 +11,6 @@
 const _desc = r'Use throwsA matcher instead of fail().';
 
 const _details = r'''
-
 Use the `throwsA` matcher instead of try-catch with `fail()`.
 
 **BAD:**
@@ -51,7 +50,7 @@
 
 ''';
 
-class UseTestThrowsMatchers extends LintRule implements NodeLintRule {
+class UseTestThrowsMatchers extends LintRule {
   UseTestThrowsMatchers()
       : super(
           name: 'use_test_throws_matchers',
diff --git a/lib/src/rules/use_to_and_as_if_applicable.dart b/lib/src/rules/use_to_and_as_if_applicable.dart
index bdb0534..7254560 100644
--- a/lib/src/rules/use_to_and_as_if_applicable.dart
+++ b/lib/src/rules/use_to_and_as_if_applicable.dart
@@ -6,18 +6,19 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 const _desc =
     r'Start the name of the method with to/_to or as/_as if applicable.';
 
 const _details = r'''
+From the [Effective Dart](https://dart.dev/guides/language/effective-dart/design):
 
-From the [design guide](https://dart.dev/guides/language/effective-dart/design):
+**PREFER** naming a method `to___()` if it copies the object's state to a new
+object.
 
-**PREFER** naming a method to___() if it copies the object's state to a new object.
-
-**PREFER** naming a method as___() if it returns a different representation backed by the original object.
+**PREFER** naming a method `as___()` if it returns a different representation
+backed by the original object.
 
 **BAD:**
 ```dart
@@ -54,9 +55,9 @@
 }
 
 bool _isVoid(TypeAnnotation? returnType) =>
-    returnType is TypeName && returnType.name.name == 'void';
+    returnType is NamedType && returnType.name.name == 'void';
 
-class UseToAndAsIfApplicable extends LintRule implements NodeLintRule {
+class UseToAndAsIfApplicable extends LintRule {
   UseToAndAsIfApplicable()
       : super(
             name: 'use_to_and_as_if_applicable',
@@ -84,10 +85,10 @@
         nodeParameters != null &&
         nodeParameters.parameters.isEmpty &&
         !_isVoid(node.returnType) &&
-        !_beginsWithAsOrTo(node.name.name) &&
-        !DartTypeUtilities.hasInheritedMethod(node) &&
+        !_beginsWithAsOrTo(node.name.lexeme) &&
+        !node.hasInheritedMethod &&
         _checkBody(node.body)) {
-      rule.reportLint(node.name);
+      rule.reportLintForToken(node.name);
     }
   }
 
diff --git a/lib/src/rules/valid_regexps.dart b/lib/src/rules/valid_regexps.dart
index 7cc6792..0c9675f 100644
--- a/lib/src/rules/valid_regexps.dart
+++ b/lib/src/rules/valid_regexps.dart
@@ -10,7 +10,6 @@
 const _desc = r'Use valid regular expression syntax.';
 
 const _details = r'''
-
 **DO** use valid regular expression syntax when creating regular expression
 instances.
 
@@ -29,7 +28,11 @@
 
 ''';
 
-class ValidRegExps extends LintRule implements NodeLintRule {
+class ValidRegExps extends LintRule {
+  static const LintCode code = LintCode(
+      'valid_regexps', 'Invalid regular expression syntax.',
+      correctionMessage: 'Try correcting the regular expression.');
+
   ValidRegExps()
       : super(
             name: 'valid_regexps',
@@ -38,6 +41,9 @@
             group: Group.errors);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this);
@@ -59,7 +65,7 @@
         return;
       }
 
-      bool isTrue(Expression e) => e is BooleanLiteral && e.value == true;
+      bool isTrue(Expression e) => e is BooleanLiteral && e.value;
 
       var unicode = args.any((arg) =>
           arg is NamedExpression &&
diff --git a/lib/src/rules/void_checks.dart b/lib/src/rules/void_checks.dart
index 7bb3a36..45c97b5 100644
--- a/lib/src/rules/void_checks.dart
+++ b/lib/src/rules/void_checks.dart
@@ -12,8 +12,7 @@
 const _desc = r"Don't assign to void.";
 
 const _details = r'''
-
-**DO NOT** assign to void.
+**DON'T** assign to void.
 
 **BAD:**
 ```dart
@@ -30,7 +29,12 @@
 ```
 ''';
 
-class VoidChecks extends LintRule implements NodeLintRule {
+class VoidChecks extends LintRule {
+  static const LintCode code = LintCode(
+      'void_checks', "Assignment to a variable of type 'void'.",
+      correctionMessage:
+          'Try removing the assignment or changing the type of the variable.');
+
   VoidChecks()
       : super(
             name: 'void_checks',
@@ -39,6 +43,9 @@
             group: Group.style);
 
   @override
+  LintCode get lintCode => code;
+
+  @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     var visitor = _Visitor(this, context);
@@ -52,21 +59,9 @@
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
 
-  final LinterContext context;
   final TypeSystem typeSystem;
 
-  _Visitor(this.rule, this.context) : typeSystem = context.typeSystem;
-
-  bool isTypeAcceptableWhenExpectingVoid(DartType type) {
-    if (type.isVoid) return true;
-    if (type.isDartCoreNull) return true;
-    if (type.isDartAsyncFuture &&
-        type is InterfaceType &&
-        isTypeAcceptableWhenExpectingVoid(type.typeArguments.first)) {
-      return true;
-    }
-    return false;
-  }
+  _Visitor(this.rule, LinterContext context) : typeSystem = context.typeSystem;
 
   bool isTypeAcceptableWhenExpectingFutureOrVoid(DartType type) {
     if (type.isDynamic) return true;
@@ -80,6 +75,17 @@
     return false;
   }
 
+  bool isTypeAcceptableWhenExpectingVoid(DartType type) {
+    if (type.isVoid) return true;
+    if (type.isDartCoreNull) return true;
+    if (type.isDartAsyncFuture &&
+        type is InterfaceType &&
+        isTypeAcceptableWhenExpectingVoid(type.typeArguments.first)) {
+      return true;
+    }
+    return false;
+  }
+
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
     var type = node.writeType;
@@ -135,6 +141,9 @@
     if (expectedType == null || type == null) {
       return;
     }
+    if (expectedType.isVoid && !type.isDynamic && node is ReturnStatement) {
+      return;
+    }
     if (expectedType.isVoid && !isTypeAcceptableWhenExpectingVoid(type)) {
       rule.reportLint(node);
     } else if (expectedType.isDartAsyncFutureOr &&
diff --git a/lib/src/test_utilities/annotation.dart b/lib/src/test_utilities/annotation.dart
index b529acf..75ea150 100644
--- a/lib/src/test_utilities/annotation.dart
+++ b/lib/src/test_utilities/annotation.dart
@@ -61,6 +61,5 @@
 
 extension on String? {
   int? toInt() => this == null ? null : int.parse(this!);
-  String? toNullIfBlank() =>
-      this == null || this!.trim().isEmpty == true ? null : this;
+  String? toNullIfBlank() => this == null || this!.trim().isEmpty ? null : this;
 }
diff --git a/lib/src/test_utilities/test_resource_provider.dart b/lib/src/test_utilities/test_resource_provider.dart
index 725f78e..696c1ee 100644
--- a/lib/src/test_utilities/test_resource_provider.dart
+++ b/lib/src/test_utilities/test_resource_provider.dart
@@ -15,22 +15,22 @@
 
 /// Builds the [DartLinter] with appropriate mock SDK, resource providers, and
 /// package config path.
-DartLinter buildDriver(LintRule? rule, File file, {String? analysisOptions}) {
+DartLinter buildDriver(LintRule rule, File file, {String? analysisOptions}) {
   var memoryResourceProvider = MemoryResourceProvider(
       context: PhysicalResourceProvider.INSTANCE.pathContext);
   var resourceProvider = TestResourceProvider(memoryResourceProvider);
 
-  var pathContext = memoryResourceProvider.pathContext;
-  String? packageConfigPath = memoryResourceProvider.convertPath(pathContext
-      .join(pathContext.dirname(file.absolute.path), '.mock_packages'));
-  if (!resourceProvider.getFile(packageConfigPath).exists) {
-    packageConfigPath = null;
-  }
+  var sdkRoot = memoryResourceProvider.getFolder(
+    memoryResourceProvider.convertPath('/sdk'),
+  );
+  createMockSdk(
+    resourceProvider: memoryResourceProvider,
+    root: sdkRoot,
+  );
 
-  var options = LinterOptions([rule!], analysisOptions)
-    ..mockSdk = MockSdk(resourceProvider: memoryResourceProvider)
-    ..resourceProvider = resourceProvider
-    ..packageConfigPath = packageConfigPath;
+  var options = LinterOptions([rule], analysisOptions)
+    ..dartSdkPath = sdkRoot.path
+    ..resourceProvider = resourceProvider;
 
   return DartLinter(options);
 }
@@ -46,6 +46,9 @@
   TestResourceProvider(this.memoryResourceProvider);
 
   @override
+  path.Context get pathContext => physicalResourceProvider.pathContext;
+
+  @override
   file_system.File getFile(String path) {
     var file = memoryResourceProvider.getFile(path);
     return file.exists ? file : physicalResourceProvider.getFile(path);
@@ -66,13 +69,6 @@
   }
 
   @override
-  Future<List<int?>> getModificationTimes(List<Source> sources) =>
-      physicalResourceProvider.getModificationTimes(sources);
-
-  @override
   file_system.Folder? getStateLocation(String pluginId) =>
       physicalResourceProvider.getStateLocation(pluginId);
-
-  @override
-  path.Context get pathContext => physicalResourceProvider.pathContext;
 }
diff --git a/lib/src/util/ascii_utils.dart b/lib/src/util/ascii_utils.dart
index 9038b90..21255fe 100644
--- a/lib/src/util/ascii_utils.dart
+++ b/lib/src/util/ascii_utils.dart
@@ -8,7 +8,7 @@
 /// See: https://github.com/dart-lang/linter/issues/1828
 library ascii_utils;
 
-import "charcodes.dart";
+import 'charcodes.dart';
 
 /// Return `true` if the given [character] is the ASCII '.' character.
 bool isDot(int character) => character == $dot;
@@ -58,3 +58,25 @@
   }
   return true;
 }
+
+extension StringExtensions on String {
+  /// Returns whether `this` is just underscores.
+  bool get isJustUnderscores {
+    if (isEmpty) {
+      return false;
+    }
+    switch (length) {
+      case 1:
+        return this == '_';
+      case 2:
+        return this == '__';
+      default:
+        for (var i = 0; i < length; i++) {
+          if (!isUnderScore(codeUnitAt(i))) {
+            return false;
+          }
+        }
+        return true;
+    }
+  }
+}
diff --git a/lib/src/util/boolean_expression_utilities.dart b/lib/src/util/boolean_expression_utilities.dart
index e8e43d0..1183f78 100644
--- a/lib/src/util/boolean_expression_utilities.dart
+++ b/lib/src/util/boolean_expression_utilities.dart
@@ -12,17 +12,17 @@
 
 class BooleanExpressionUtilities {
   static HashSet<TokenType> BOOLEAN_OPERATIONS =
-      HashSet.from(const [TokenType.AMPERSAND_AMPERSAND, TokenType.BAR_BAR]);
+      HashSet.of(const [TokenType.AMPERSAND_AMPERSAND, TokenType.BAR_BAR]);
 
   static HashSet<TokenType> EQUALITY_OPERATIONS =
-      HashSet.from(const [TokenType.EQ_EQ, TokenType.BANG_EQ]);
+      HashSet.of(const [TokenType.EQ_EQ, TokenType.BANG_EQ]);
 
-  static HashMap<TokenType, TokenType> IMPLICATIONS = HashMap.from(const {
+  static HashMap<TokenType, TokenType> IMPLICATIONS = HashMap.of(const {
     TokenType.GT: TokenType.GT_EQ,
     TokenType.LT: TokenType.LT_EQ,
   });
 
-  static HashMap<TokenType, TokenType> NEGATIONS = HashMap.from(const {
+  static HashMap<TokenType, TokenType> NEGATIONS = HashMap.of(const {
     TokenType.EQ_EQ: TokenType.BANG_EQ,
     TokenType.BANG_EQ: TokenType.EQ_EQ,
     TokenType.GT: TokenType.LT_EQ,
@@ -32,7 +32,7 @@
   });
 
   static HashSet<TokenType> TRICHOTOMY_OPERATORS =
-      HashSet.from(const [TokenType.EQ_EQ, TokenType.LT, TokenType.GT]);
+      HashSet.of(const [TokenType.EQ_EQ, TokenType.LT, TokenType.GT]);
 
-  static final HashSet<TokenType> COMPARISONS = HashSet.from(NEGATIONS.keys);
+  static final HashSet<TokenType> COMPARISONS = HashSet.of(NEGATIONS.keys);
 }
diff --git a/lib/src/util/charcodes.dart b/lib/src/util/charcodes.dart
index 5613dc5..3966d23 100644
--- a/lib/src/util/charcodes.dart
+++ b/lib/src/util/charcodes.dart
@@ -15,7 +15,6 @@
 const int $backslash = 0x5c;
 
 /// Character `_`.
-// ignore: constant_identifier_names
 const int $_ = 0x5f;
 
 /// Character `a`.
diff --git a/lib/src/util/condition_scope_visitor.dart b/lib/src/util/condition_scope_visitor.dart
index aa81a6f..ed9b69f 100644
--- a/lib/src/util/condition_scope_visitor.dart
+++ b/lib/src/util/condition_scope_visitor.dart
@@ -8,10 +8,10 @@
 import 'package:analyzer/dart/element/element.dart';
 
 import '../analyzer.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 Element? _getLeftElement(AssignmentExpression assignment) =>
-    DartTypeUtilities.getCanonicalElement(assignment.writeElement);
+    assignment.writeElement?.canonicalElement;
 
 List<Expression?> _splitConjunctions(Expression? rawExpression) {
   var expression = rawExpression?.unParenthesized;
@@ -40,18 +40,16 @@
 }
 
 class ConditionScope {
-  final environment = <_ExpressionBox>[];
+  final environment = <ExpressionBox>[];
   final ConditionScope? outer;
 
   ConditionScope(this.outer);
 
-  void add(_ExpressionBox? e) {
-    if (e != null) {
-      environment.add(e);
-    }
+  void add(ExpressionBox e) {
+    environment.add(e);
   }
 
-  void addAll(Iterable<_ExpressionBox> expressions) {
+  void addAll(Iterable<ExpressionBox> expressions) {
     environment.addAll(expressions);
   }
 
@@ -62,17 +60,20 @@
     return expressions;
   }
 
-  Iterable<_ExpressionBox> getUndefinedExpressions() =>
+  Iterable<ExpressionBox> getUndefinedExpressions() =>
       environment.whereType<_UndefinedExpression>();
 
   void _recursiveGetExpressions(
-      List<Expression?> expressions, Iterable<Element?> elements, bool? value) {
+      List<Expression> expressions, Iterable<Element?> elements, bool? value) {
     for (var element in environment.reversed) {
       if (element.haveToStop(elements)) {
         return;
       }
       if (element is _ConditionExpression && element.value == value) {
-        expressions.add(element.expression);
+        var expression = element.expression;
+        if (expression != null) {
+          expressions.add(expression);
+        }
       }
     }
     outer?._recursiveGetExpressions(expressions, elements, value);
@@ -296,7 +297,7 @@
     breakScope.deleteBreaksWithTarget(node);
   }
 
-  void _addElementToEnvironment(_ExpressionBox? e) {
+  void _addElementToEnvironment(ExpressionBox? e) {
     if (e != null) {
       outerScope?.add(e);
     }
@@ -322,8 +323,7 @@
 
   bool _isLastStatementAnExitStatement(Statement? statement) {
     if (statement is Block) {
-      return _isLastStatementAnExitStatement(
-          DartTypeUtilities.getLastStatementInBlock(statement));
+      return _isLastStatementAnExitStatement(statement.lastStatement);
     } else {
       if (statement is BreakStatement) {
         return statement.label == null;
@@ -350,7 +350,9 @@
         return false;
       }
 
-      for (var ref in DartTypeUtilities.traverseNodesInDFS(condition)) {
+      // todo(pq): migrate away from `traverseNodesInDFS` (https://github.com/dart-lang/linter/issues/3745)
+      // ignore: deprecated_member_use_from_same_package
+      for (var ref in condition.traverseNodesInDFS()) {
         if (ref is SimpleIdentifier) {
           var element = ref.staticElement;
           if (element == null) {
@@ -397,9 +399,13 @@
   }
 }
 
-class _ConditionExpression extends _ExpressionBox {
-  Expression? expression;
-  bool value;
+abstract class ExpressionBox {
+  bool haveToStop(Iterable<Element?> elements);
+}
+
+class _ConditionExpression extends ExpressionBox {
+  final Expression? expression;
+  final bool value;
 
   _ConditionExpression(this.expression, {this.value = true});
 
@@ -410,11 +416,7 @@
   String toString() => '$expression is $value';
 }
 
-abstract class _ExpressionBox {
-  bool haveToStop(Iterable<Element?> elements);
-}
-
-class _UndefinedAllExpression extends _ExpressionBox {
+class _UndefinedAllExpression extends ExpressionBox {
   @override
   bool haveToStop(Iterable<Element?> elements) => true;
 
@@ -422,14 +424,8 @@
   String toString() => '*All* got undefined';
 }
 
-class _UndefinedExpression extends _ExpressionBox {
-  Element element;
-
-  static _UndefinedExpression? forElement(Element? element) {
-    var canonicalElement = DartTypeUtilities.getCanonicalElement(element);
-    if (canonicalElement == null) return null;
-    return _UndefinedExpression._internal(canonicalElement);
-  }
+class _UndefinedExpression extends ExpressionBox {
+  final Element element;
 
   _UndefinedExpression._internal(this.element);
 
@@ -438,4 +434,10 @@
 
   @override
   String toString() => '$element got undefined';
+
+  static _UndefinedExpression? forElement(Element? element) {
+    var canonicalElement = element?.canonicalElement;
+    if (canonicalElement == null) return null;
+    return _UndefinedExpression._internal(canonicalElement);
+  }
 }
diff --git a/lib/src/util/dart_type_utilities.dart b/lib/src/util/dart_type_utilities.dart
index daba59d..33f89f9 100644
--- a/lib/src/util/dart_type_utilities.dart
+++ b/lib/src/util/dart_type_utilities.dart
@@ -5,542 +5,261 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/src/dart/element/member.dart'; // ignore: implementation_imports
 import 'package:analyzer/src/dart/element/type.dart'; // ignore: implementation_imports
 
 import '../analyzer.dart';
 import '../ast.dart';
+import '../extensions.dart';
 
 typedef AstNodePredicate = bool Function(AstNode node);
 
-class DartTypeUtilities {
-  static EnumLikeClassDescription? asEnumLikeClass(ClassElement classElement) {
-    // See discussion: https://github.com/dart-lang/linter/issues/2083
-    //
-
-    // Must be concrete.
-    if (classElement.isAbstract) {
-      return null;
-    }
-
-    // With only private non-factory constructors.
-    for (var cons in classElement.constructors) {
-      if (!cons.isPrivate || cons.isFactory) {
-        return null;
+bool argumentsMatchParameters(
+    NodeList<Expression> arguments, NodeList<FormalParameter> parameters) {
+  var namedParameters = <String, Element?>{};
+  var namedArguments = <String, Element>{};
+  var positionalParameters = <Element?>[];
+  var positionalArguments = <Element>[];
+  for (var parameter in parameters) {
+    var identifier = parameter.name;
+    if (identifier != null) {
+      if (parameter.isNamed) {
+        namedParameters[identifier.lexeme] = parameter.declaredElement;
+      } else {
+        positionalParameters.add(parameter.declaredElement);
       }
     }
-
-    var type = classElement.thisType;
-
-    // And 2 or more static const fields whose type is the enclosing class.
-    var enumConstantNames = <String>[];
-    for (var field in classElement.fields) {
-      // Ensure static const.
-      if (field.isSynthetic || !field.isConst || !field.isStatic) {
-        continue;
-      }
-      // Check for type equality.
-      if (field.type != type) {
-        continue;
-      }
-      enumConstantNames.add(field.name);
-    }
-    if (enumConstantNames.length < 2) {
-      return null;
-    }
-
-    // And no subclasses in the defining library.
-    var compilationUnit = classElement.library.definingCompilationUnit;
-    for (var cls in compilationUnit.classes) {
-      InterfaceType? classType = cls.thisType;
-      do {
-        classType = classType?.superclass;
-        if (classType == type) {
-          return null;
-        }
-      } while (classType != null && !classType.isDartCoreObject);
-    }
-
-    return EnumLikeClassDescription(enumConstantNames);
   }
-
-  /// Return whether the canonical elements of two elements are equal.
-  static bool canonicalElementsAreEqual(Element? element1, Element? element2) =>
-      getCanonicalElement(element1) == getCanonicalElement(element2);
-
-  /// Returns whether the canonical elements from two nodes are equal.
-  ///
-  /// As in, [getCanonicalElementFromIdentifier], the two nodes must be
-  /// [Expression]s in order to be compared (otherwise `false` is returned).
-  ///
-  /// The two nodes must both be a [SimpleIdentifier], [PrefixedIdentifier], or
-  /// [PropertyAccess] (otherwise `false` is returned).
-  ///
-  /// If the two nodes are PrefixedIdentifiers, or PropertyAccess nodes, then
-  /// `true` is returned only if their canonical elements are equal, in
-  /// addition to their prefixes' and targets' (respectfully) canonical
-  /// elements.
-  ///
-  /// There is an inherent assumption about pure getters. For example:
-  ///
-  ///     A a1 = ...
-  ///     A a2 = ...
-  ///     a1.b.c; // statement 1
-  ///     a2.b.c; // statement 2
-  ///     a1.b.c; // statement 3
-  ///
-  /// The canonical elements from statements 1 and 2 are different, because a1
-  /// is not the same element as a2.  The canonical elements from statements 1
-  /// and 3 are considered to be equal, even though `A.b` may have side effects
-  /// which alter the returned value.
-  static bool canonicalElementsFromIdentifiersAreEqual(
-      Expression? rawExpression1, Expression? rawExpression2) {
-    if (rawExpression1 == null || rawExpression2 == null) return false;
-
-    var expression1 = rawExpression1.unParenthesized;
-    var expression2 = rawExpression2.unParenthesized;
-
-    if (expression1 is SimpleIdentifier) {
-      return expression2 is SimpleIdentifier &&
-          canonicalElementsAreEqual(getWriteOrReadElement(expression1),
-              getWriteOrReadElement(expression2));
+  for (var argument in arguments) {
+    if (argument is NamedExpression) {
+      var element = argument.expression.canonicalElement;
+      if (element == null) {
+        return false;
+      }
+      namedArguments[argument.name.label.name] = element;
+    } else {
+      var element = argument.canonicalElement;
+      if (element == null) {
+        return false;
+      }
+      positionalArguments.add(element);
     }
-
-    if (expression1 is PrefixedIdentifier) {
-      return expression2 is PrefixedIdentifier &&
-          canonicalElementsAreEqual(expression1.prefix.staticElement,
-              expression2.prefix.staticElement) &&
-          canonicalElementsAreEqual(
-              getWriteOrReadElement(expression1.identifier),
-              getWriteOrReadElement(expression2.identifier));
-    }
-
-    if (expression1 is PropertyAccess && expression2 is PropertyAccess) {
-      var target1 = expression1.target;
-      var target2 = expression2.target;
-      return canonicalElementsFromIdentifiersAreEqual(target1, target2) &&
-          canonicalElementsAreEqual(
-              getWriteOrReadElement(expression1.propertyName),
-              getWriteOrReadElement(expression2.propertyName));
-    }
-
+  }
+  if (positionalParameters.length != positionalArguments.length ||
+      namedParameters.keys.length != namedArguments.keys.length) {
     return false;
   }
-
-  static bool extendsClass(
-          DartType? type, String? className, String? library) =>
-      _extendsClass(type, <ClassElement>{}, className, library);
-
-  static Element? getCanonicalElement(Element? element) {
-    if (element is PropertyAccessorElement) {
-      var variable = element.variable;
-      if (variable is FieldMember) {
-        // A field element defined in a parameterized type where the values of
-        // the type parameters are known.
-        //
-        // This concept should be invisible when comparing FieldElements, but a
-        // bug in the analyzer causes FieldElements to not evaluate as
-        // equivalent to equivalent FieldMembers. See
-        // https://github.com/dart-lang/sdk/issues/35343.
-        return variable.declaration;
-      } else {
-        return variable;
-      }
-    } else {
-      return element;
-    }
-  }
-
-  static Element? getCanonicalElementFromIdentifier(AstNode? rawNode) {
-    if (rawNode is Expression) {
-      var node = rawNode.unParenthesized;
-      if (node is Identifier) {
-        return getCanonicalElement(node.staticElement);
-      } else if (node is PropertyAccess) {
-        return getCanonicalElement(node.propertyName.staticElement);
-      }
-    }
-    return null;
-  }
-
-  static Iterable<InterfaceType> getImplementedInterfaces(InterfaceType type) {
-    void recursiveCall(InterfaceType? type, Set<ClassElement> alreadyVisited,
-        List<InterfaceType> interfaceTypes) {
-      if (type == null || !alreadyVisited.add(type.element)) {
-        return;
-      }
-      interfaceTypes.add(type);
-      recursiveCall(type.superclass, alreadyVisited, interfaceTypes);
-      for (var interface in type.interfaces) {
-        recursiveCall(interface, alreadyVisited, interfaceTypes);
-      }
-      for (var mixin in type.mixins) {
-        recursiveCall(mixin, alreadyVisited, interfaceTypes);
-      }
-    }
-
-    var interfaceTypes = <InterfaceType>[];
-    recursiveCall(type, <ClassElement>{}, interfaceTypes);
-    return interfaceTypes;
-  }
-
-  static Statement? getLastStatementInBlock(Block node) {
-    if (node.statements.isEmpty) {
-      return null;
-    }
-    var lastStatement = node.statements.last;
-    if (lastStatement is Block) {
-      return getLastStatementInBlock(lastStatement);
-    }
-    return lastStatement;
-  }
-
-  static bool hasInheritedMethod(MethodDeclaration node) =>
-      lookUpInheritedMethod(node) != null;
-
-  static bool implementsAnyInterface(
-      DartType type, Iterable<InterfaceTypeDefinition> definitions) {
-    bool isAnyInterface(InterfaceType i) =>
-        definitions.any((d) => isInterface(i, d.name, d.library));
-
-    if (type is InterfaceType) {
-      var element = type.element;
-      return isAnyInterface(type) ||
-          !element.isSynthetic && element.allSupertypes.any(isAnyInterface);
-    } else {
+  for (var i = 0; i < positionalArguments.length; i++) {
+    if (positionalArguments[i] != positionalParameters[i]) {
       return false;
     }
   }
 
-  static bool implementsInterface(
-      DartType? type, String interface, String library) {
-    if (type is! InterfaceType) {
+  for (var key in namedParameters.keys) {
+    if (namedParameters[key] != namedArguments[key]) {
       return false;
     }
-    var interfaceType = type;
-    bool predicate(InterfaceType i) => isInterface(i, interface, library);
-    var element = interfaceType.element;
-    return predicate(interfaceType) ||
-        !element.isSynthetic && element.allSupertypes.any(predicate);
   }
 
-  /// todo (pq): unify and  `isInterface` into a shared method: `isInterfaceType`
-  static bool isClass(DartType? type, String? className, String? library) =>
-      type is InterfaceType &&
-      type.element.name == className &&
-      type.element.library.name == library;
+  return true;
+}
 
-  static bool isClassElement(
-          ClassElement element, String className, String library) =>
-      element.name == className && element.library.name == library;
+/// Returns whether the canonical elements of [element1] and [element2] are
+/// equal.
+bool canonicalElementsAreEqual(Element? element1, Element? element2) =>
+    element1?.canonicalElement == element2?.canonicalElement;
 
-  static bool isConstructorElement(ConstructorElement? element,
-          {required String uriStr,
-          required String className,
-          required String constructorName}) =>
-      element != null &&
-      element.library.name == uriStr &&
-      element.enclosingElement.name == className &&
-      element.name == constructorName;
+/// Returns whether the canonical elements from two nodes are equal.
+///
+/// As in, [NullableAstNodeExtension.canonicalElement], the two nodes must be
+/// [Expression]s in order to be compared (otherwise `false` is returned).
+///
+/// The two nodes must both be a [SimpleIdentifier], [PrefixedIdentifier], or
+/// [PropertyAccess] (otherwise `false` is returned).
+///
+/// If the two nodes are PrefixedIdentifiers, or PropertyAccess nodes, then
+/// `true` is returned only if their canonical elements are equal, in
+/// addition to their prefixes' and targets' (respectfully) canonical
+/// elements.
+///
+/// There is an inherent assumption about pure getters. For example:
+///
+///     A a1 = ...
+///     A a2 = ...
+///     a1.b.c; // statement 1
+///     a2.b.c; // statement 2
+///     a1.b.c; // statement 3
+///
+/// The canonical elements from statements 1 and 2 are different, because a1
+/// is not the same element as a2.  The canonical elements from statements 1
+/// and 3 are considered to be equal, even though `A.b` may have side effects
+/// which alter the returned value.
+bool canonicalElementsFromIdentifiersAreEqual(
+    Expression? rawExpression1, Expression? rawExpression2) {
+  if (rawExpression1 == null || rawExpression2 == null) return false;
 
-  static bool isInterface(
-          InterfaceType type, String interface, String library) =>
-      type.element.name == interface && type.element.library.name == library;
+  var expression1 = rawExpression1.unParenthesized;
+  var expression2 = rawExpression2.unParenthesized;
 
-  static bool isNonNullable(LinterContext context, DartType? type) =>
-      type != null && context.typeSystem.isNonNullable(type);
-
-  static bool isNullLiteral(Expression? expression) =>
-      expression?.unParenthesized is NullLiteral;
-
-  static PropertyAccessorElement? lookUpGetter(MethodDeclaration node) {
-    var declaredElement = node.declaredElement;
-    if (declaredElement == null) {
-      return null;
-    }
-    var parent = declaredElement.enclosingElement;
-    if (parent is ClassElement) {
-      return parent.lookUpGetter(node.name.name, declaredElement.library);
-    }
-    if (parent is ExtensionElement) {
-      return parent.getGetter(node.name.name);
-    }
-    return null;
+  if (expression1 is SimpleIdentifier) {
+    return expression2 is SimpleIdentifier &&
+        canonicalElementsAreEqual(getWriteOrReadElement(expression1),
+            getWriteOrReadElement(expression2));
   }
 
-  static PropertyAccessorElement? lookUpInheritedConcreteGetter(
-      MethodDeclaration node) {
-    var declaredElement = node.declaredElement;
-    if (declaredElement == null) {
-      return null;
-    }
-    var parent = declaredElement.enclosingElement;
-    if (parent is ClassElement) {
-      return parent.lookUpInheritedConcreteGetter(
-          node.name.name, declaredElement.library);
-    }
-    // Extensions don't inherit.
-    return null;
+  if (expression1 is PrefixedIdentifier) {
+    return expression2 is PrefixedIdentifier &&
+        canonicalElementsAreEqual(expression1.prefix.staticElement,
+            expression2.prefix.staticElement) &&
+        canonicalElementsAreEqual(getWriteOrReadElement(expression1.identifier),
+            getWriteOrReadElement(expression2.identifier));
   }
 
-  static MethodElement? lookUpInheritedConcreteMethod(MethodDeclaration node) {
-    var declaredElement = node.declaredElement;
-    if (declaredElement != null) {
-      var parent = declaredElement.enclosingElement;
-      if (parent is ClassElement) {
-        return parent.lookUpInheritedConcreteMethod(
-            node.name.name, declaredElement.library);
-      }
-    }
-    // Extensions don't inherit.
-    return null;
+  if (expression1 is PropertyAccess && expression2 is PropertyAccess) {
+    var target1 = expression1.target;
+    var target2 = expression2.target;
+    return canonicalElementsFromIdentifiersAreEqual(target1, target2) &&
+        canonicalElementsAreEqual(
+            getWriteOrReadElement(expression1.propertyName),
+            getWriteOrReadElement(expression2.propertyName));
   }
 
-  static PropertyAccessorElement? lookUpInheritedConcreteSetter(
-      MethodDeclaration node) {
-    var declaredElement = node.declaredElement;
-    if (declaredElement != null) {
-      var parent = declaredElement.enclosingElement;
-      if (parent is ClassElement) {
-        return parent.lookUpInheritedConcreteSetter(
-            node.name.name, declaredElement.library);
-      }
-    }
-    // Extensions don't inherit.
-    return null;
-  }
+  return false;
+}
 
-  static MethodElement? lookUpInheritedMethod(MethodDeclaration node) {
-    var declaredElement = node.declaredElement;
-    if (declaredElement != null) {
-      var parent = declaredElement.enclosingElement;
-      if (parent is ClassElement) {
-        return parent.lookUpInheritedMethod(
-            node.name.name, declaredElement.library);
-      }
-    }
-    return null;
-  }
-
-  static PropertyAccessorElement? lookUpSetter(MethodDeclaration node) {
-    var declaredElement = node.declaredElement;
-    if (declaredElement != null) {
-      var parent = declaredElement.enclosingElement;
-      if (parent is ClassElement) {
-        return parent.lookUpSetter(node.name.name, declaredElement.library);
-      }
-      if (parent is ExtensionElement) {
-        return parent.getSetter(node.name.name);
-      }
-    }
-    return null;
-  }
-
-  static bool matchesArgumentsWithParameters(
-      NodeList<Expression> arguments, NodeList<FormalParameter> parameters) {
-    var namedParameters = <String, Element?>{};
-    var namedArguments = <String, Element>{};
-    var positionalParameters = <Element?>[];
-    var positionalArguments = <Element>[];
-    for (var parameter in parameters) {
-      var identifier = parameter.identifier;
-      if (identifier != null) {
-        if (parameter.isNamed) {
-          namedParameters[identifier.name] = identifier.staticElement;
-        } else {
-          positionalParameters.add(identifier.staticElement);
-        }
-      }
-    }
-    for (var argument in arguments) {
-      if (argument is NamedExpression) {
-        var element = DartTypeUtilities.getCanonicalElementFromIdentifier(
-            argument.expression);
-        if (element == null) {
-          return false;
-        }
-        namedArguments[argument.name.label.name] = element;
-      } else {
-        var element =
-            DartTypeUtilities.getCanonicalElementFromIdentifier(argument);
-        if (element == null) {
-          return false;
-        }
-        positionalArguments.add(element);
-      }
-    }
-    if (positionalParameters.length != positionalArguments.length ||
-        namedParameters.keys.length != namedArguments.keys.length) {
-      return false;
-    }
-    for (var i = 0; i < positionalArguments.length; i++) {
-      if (positionalArguments[i] != positionalParameters[i]) {
-        return false;
-      }
-    }
-
-    for (var key in namedParameters.keys) {
-      if (namedParameters[key] != namedArguments[key]) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  static bool overridesMethod(MethodDeclaration node) {
-    var parent = node.parent;
-    if (parent is! ClassOrMixinDeclaration) {
-      return false;
-    }
-    var name = node.declaredElement?.name;
-    if (name == null) {
-      return false;
-    }
-    var clazz = parent;
-    var classElement = clazz.declaredElement;
-    if (classElement == null) {
-      return false;
-    }
-    var library = classElement.library;
-    return classElement.allSupertypes
-        .map(node.isGetter
-            ? (InterfaceType t) => t.lookUpGetter2
-            : node.isSetter
-                ? (InterfaceType t) => t.lookUpSetter2
-                : (InterfaceType t) => t.lookUpMethod2)
-        .any((lookUp) => lookUp(name, library) != null);
-  }
-
-  /// Builds the list resulting from traversing the node in DFS and does not
-  /// include the node itself, it excludes the nodes for which the exclusion
-  /// predicate returns true, if not provided, all is included.
-  static Iterable<AstNode> traverseNodesInDFS(AstNode node,
-      {AstNodePredicate? excludeCriteria}) {
-    var nodes = <AstNode>{};
-    void recursiveCall(node) {
-      if (node is AstNode &&
-          (excludeCriteria == null || !excludeCriteria(node))) {
-        nodes.add(node);
-        node.childEntities.forEach(recursiveCall);
-      }
-    }
-
-    node.childEntities.forEach(recursiveCall);
-    return nodes;
-  }
-
-  /// Returns whether [leftType] and [rightType] are _definitely_ unrelated.
-  ///
-  /// For the purposes of this function, here are some "relation" rules:
-  /// * `dynamic` and `Null` are considered related to any other type.
-  /// * Two types which are equal modulo nullability are considered related,
-  ///   e.g. `int` and `int`, `String` and `String?`, `List<String>` and
-  ///   `List<String>`, `List<T>` and `List<T>`, and type variables `A` and `A`.
-  /// * Two types such that one is a subtype of the other, modulo nullability,
-  ///   such as `List<dynamic>` and `Iterable<dynamic>`, and type variables `A`
-  ///   and `B` where `A extends B`, are considered related.
-  /// * Two interface types:
-  ///   * are related if they represent the same class, modulo type arguments,
-  ///     modulo nullability, and each of their pair-wise type arguments are
-  ///     related, e.g. `List<dynamic>` and `List<int>`, and `Future<T>` and
-  ///     `Future<S>` where `S extends T`.
-  ///   * are unrelated if [leftType]'s supertype is [Object].
-  ///   * are related if their supertypes are equal, e.g. `List<dynamic>` and
-  ///     `Set<dynamic>`.
-  /// * Two type variables are related if their bounds are related.
-  /// * Otherwise, the types are related.
-  // TODO(srawlins): typedefs and functions in general.
-  static bool unrelatedTypes(
-      TypeSystem typeSystem, DartType? leftType, DartType? rightType) {
-    // If we don't have enough information, or can't really compare the types,
-    // return false as they _might_ be related.
-    if (leftType == null ||
-        leftType.isBottom ||
-        leftType.isDynamic ||
-        rightType == null ||
-        rightType.isBottom ||
-        rightType.isDynamic) {
-      return false;
-    }
-    var promotedLeftType = typeSystem.promoteToNonNull(leftType);
-    var promotedRightType = typeSystem.promoteToNonNull(rightType);
-    if (promotedLeftType == promotedRightType ||
-        typeSystem.isSubtypeOf(promotedLeftType, promotedRightType) ||
-        typeSystem.isSubtypeOf(promotedRightType, promotedLeftType)) {
-      return false;
-    }
-    if (promotedLeftType is InterfaceType &&
-        promotedRightType is InterfaceType) {
-      // In this case, [leftElement] and [rightElement] each represent
-      // the same class, like `int`, or `Iterable<String>`.
-      var leftElement = promotedLeftType.element;
-      var rightElement = promotedRightType.element;
-      if (leftElement == rightElement) {
-        // In this case, [leftElement] and [rightElement] represent the same
-        // class, modulo generics, e.g. `List<int>` and `List<dynamic>`. Now we
-        // need to check type arguments.
-        var leftTypeArguments = promotedLeftType.typeArguments;
-        var rightTypeArguments = promotedRightType.typeArguments;
-        if (leftTypeArguments.length != rightTypeArguments.length) {
-          // I cannot think of how we would enter this block, but it guards
-          // against RangeError below.
-          return false;
-        }
-        for (var i = 0; i < leftTypeArguments.length; i++) {
-          // If any of the pair-wise type arguments are unrelated, then
-          // [leftType] and [rightType] are unrelated.
-          if (unrelatedTypes(
-              typeSystem, leftTypeArguments[i], rightTypeArguments[i])) {
-            return true;
-          }
-        }
-        // Otherwise, they might be related.
-        return false;
-      } else {
-        return (leftElement.supertype?.isDartCoreObject ?? false) ||
-            leftElement.supertype != rightElement.supertype;
-      }
-    } else if (promotedLeftType is TypeParameterType &&
-        promotedRightType is TypeParameterType) {
-      return unrelatedTypes(typeSystem, promotedLeftType.element.bound,
-          promotedRightType.element.bound);
-    } else if (promotedLeftType is FunctionType) {
-      if (_isFunctionTypeUnrelatedToType(promotedLeftType, promotedRightType)) {
-        return true;
-      }
-    } else if (promotedRightType is FunctionType) {
-      if (_isFunctionTypeUnrelatedToType(promotedRightType, promotedLeftType)) {
-        return true;
-      }
-    }
+/// Returns whether [leftType] and [rightType] are _definitely_ unrelated.
+///
+/// For the purposes of this function, here are some "relation" rules:
+/// * `dynamic` and `Null` are considered related to any other type.
+/// * Two types which are equal modulo nullability are considered related,
+///   e.g. `int` and `int`, `String` and `String?`, `List<String>` and
+///   `List<String>`, `List<T>` and `List<T>`, and type variables `A` and `A`.
+/// * Two types such that one is a subtype of the other, modulo nullability,
+///   such as `List<dynamic>` and `Iterable<dynamic>`, and type variables `A`
+///   and `B` where `A extends B`, are considered related.
+/// * Two interface types:
+///   * are related if they represent the same class, modulo type arguments,
+///     modulo nullability, and each of their pair-wise type arguments are
+///     related, e.g. `List<dynamic>` and `List<int>`, and `Future<T>` and
+///     `Future<S>` where `S extends T`.
+///   * are unrelated if [leftType]'s supertype is [Object].
+///   * are related if their supertypes are equal, e.g. `List<dynamic>` and
+///     `Set<dynamic>`.
+/// * Two type variables are related if their bounds are related.
+/// * Otherwise, the types are related.
+// TODO(srawlins): typedefs and functions in general.
+bool typesAreUnrelated(
+    TypeSystem typeSystem, DartType? leftType, DartType? rightType) {
+  // If we don't have enough information, or can't really compare the types,
+  // return false as they _might_ be related.
+  if (leftType == null ||
+      leftType.isBottom ||
+      leftType.isDynamic ||
+      rightType == null ||
+      rightType.isBottom ||
+      rightType.isDynamic) {
     return false;
   }
-
-  static bool _extendsClass(DartType? type, Set<ClassElement> seenTypes,
-          String? className, String? library) =>
-      type is InterfaceType &&
-      seenTypes.add(type.element) &&
-      (isClass(type, className, library) ||
-          _extendsClass(type.superclass, seenTypes, className, library));
-
-  static bool _isFunctionTypeUnrelatedToType(
-      FunctionType type1, DartType type2) {
-    if (type2 is FunctionType) {
+  var promotedLeftType = typeSystem.promoteToNonNull(leftType);
+  var promotedRightType = typeSystem.promoteToNonNull(rightType);
+  if (promotedLeftType == promotedRightType ||
+      typeSystem.isSubtypeOf(promotedLeftType, promotedRightType) ||
+      typeSystem.isSubtypeOf(promotedRightType, promotedLeftType)) {
+    return false;
+  }
+  if (promotedLeftType is InterfaceType && promotedRightType is InterfaceType) {
+    // In this case, [leftElement] and [rightElement] each represent
+    // the same class, like `int`, or `Iterable<String>`.
+    var leftElement = promotedLeftType.element;
+    var rightElement = promotedRightType.element;
+    if (leftElement == rightElement) {
+      // In this case, [leftElement] and [rightElement] represent the same
+      // class, modulo generics, e.g. `List<int>` and `List<dynamic>`. Now we
+      // need to check type arguments.
+      var leftTypeArguments = promotedLeftType.typeArguments;
+      var rightTypeArguments = promotedRightType.typeArguments;
+      if (leftTypeArguments.length != rightTypeArguments.length) {
+        // I cannot think of how we would enter this block, but it guards
+        // against RangeError below.
+        return false;
+      }
+      for (var i = 0; i < leftTypeArguments.length; i++) {
+        // If any of the pair-wise type arguments are unrelated, then
+        // [leftType] and [rightType] are unrelated.
+        if (typesAreUnrelated(
+            typeSystem, leftTypeArguments[i], rightTypeArguments[i])) {
+          return true;
+        }
+      }
+      // Otherwise, they might be related.
       return false;
+    } else {
+      return (leftElement.supertype?.isDartCoreObject ?? false) ||
+          leftElement.supertype != rightElement.supertype;
     }
+  } else if (promotedLeftType is TypeParameterType &&
+      promotedRightType is TypeParameterType) {
+    return typesAreUnrelated(typeSystem, promotedLeftType.element.bound,
+        promotedRightType.element.bound);
+  } else if (promotedLeftType is FunctionType) {
+    if (_isFunctionTypeUnrelatedToType(promotedLeftType, promotedRightType)) {
+      return true;
+    }
+  } else if (promotedRightType is FunctionType) {
+    if (_isFunctionTypeUnrelatedToType(promotedRightType, promotedLeftType)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool _isFunctionTypeUnrelatedToType(FunctionType type1, DartType type2) {
+  if (type2 is FunctionType) {
+    return false;
+  }
+  if (type2 is InterfaceType) {
     var element2 = type2.element;
     if (element2 is ClassElement &&
         element2.lookUpConcreteMethod('call', element2.library) != null) {
       return false;
     }
-    return true;
   }
+  return true;
 }
 
-class EnumLikeClassDescription {
-  List<String> enumConstantNames;
-  EnumLikeClassDescription(this.enumConstantNames);
+class DartTypeUtilities {
+  @Deprecated('Replace with `type.extendsClass`')
+  static bool extendsClass(
+          DartType? type, String? className, String? library) =>
+      type.extendsClass(className, library!);
+
+  @Deprecated('Replace with `rawNode.canonicalElement`')
+  static Element? getCanonicalElementFromIdentifier(AstNode? rawNode) =>
+      rawNode.canonicalElement;
+
+  @Deprecated('Replace with `type.implementsInterface`')
+  static bool implementsInterface(
+          DartType? type, String interface, String library) =>
+      type.implementsInterface(interface, library);
+
+  // todo(pq): remove and replace w/ an extension (pending internal migration)
+  @Deprecated('Slated for removal')
+  static bool isClass(DartType? type, String? className, String? library) =>
+      type is InterfaceType &&
+      type.element.name == className &&
+      type.element.library.name == library;
+
+  @Deprecated('Replace with `expression.isNullLiteral`')
+  static bool isNullLiteral(Expression? expression) => expression.isNullLiteral;
+
+  @Deprecated('Use `argumentsMatchParameters`')
+  static bool matchesArgumentsWithParameters(NodeList<Expression> arguments,
+          NodeList<FormalParameter> parameters) =>
+      argumentsMatchParameters(arguments, parameters);
+
+  @Deprecated('Replace with `node.traverseNodesInDFS`')
+  static Iterable<AstNode> traverseNodesInDFS(AstNode node,
+          {AstNodePredicate? excludeCriteria}) =>
+      node.traverseNodesInDFS(excludeCriteria: excludeCriteria);
 }
 
 class InterfaceTypeDefinition {
@@ -569,17 +288,19 @@
   ///
   /// If `this` is a type variable, then the type-for-interface-check of its
   /// promoted bound or bound is returned. Otherwise, `this` is returned.
+  // TODO(srawlins): Move to extensions.dart.
   DartType get typeForInterfaceCheck {
-    if (this is TypeParameterType) {
-      if (this is TypeParameterTypeImpl) {
-        var promotedType = (this as TypeParameterTypeImpl).promotedBound;
+    var self = this;
+    if (self is TypeParameterType) {
+      if (self is TypeParameterTypeImpl) {
+        var promotedType = self.promotedBound;
         if (promotedType != null) {
           return promotedType.typeForInterfaceCheck;
         }
       }
-      return (this as TypeParameterType).bound.typeForInterfaceCheck;
+      return self.bound.typeForInterfaceCheck;
     } else {
-      return this;
+      return self;
     }
   }
 }
diff --git a/lib/src/util/flutter_utils.dart b/lib/src/util/flutter_utils.dart
index a62e31a..2e14244 100644
--- a/lib/src/util/flutter_utils.dart
+++ b/lib/src/util/flutter_utils.dart
@@ -6,6 +6,7 @@
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
 
+import '../extensions.dart';
 import '../util/dart_type_utilities.dart';
 
 var _collectionInterfaces = <InterfaceTypeDefinition>[
@@ -31,6 +32,11 @@
 bool isExactWidgetTypeContainer(DartType? type) =>
     _flutter.isExactWidgetTypeContainer(type);
 
+bool isExactWidgetTypeSizedBox(DartType? type) =>
+    _flutter.isExactWidgetTypeSizedBox(type);
+
+bool isKDebugMode(Element? element) => _flutter.isKDebugMode(element);
+
 bool isStatefulWidget(ClassElement? element) =>
     _flutter.isStatefulWidget(element);
 
@@ -39,7 +45,7 @@
     return true;
   }
   if (type is InterfaceType &&
-      DartTypeUtilities.implementsAnyInterface(type, _collectionInterfaces)) {
+      type.implementsAnyInterface(_collectionInterfaces)) {
     return type.element.typeParameters.length == 1 &&
         isWidgetProperty(type.typeArguments.first);
   }
@@ -54,20 +60,25 @@
   static const _nameStatefulWidget = 'StatefulWidget';
   static const _nameWidget = 'Widget';
   static const _nameContainer = 'Container';
+  static const _nameSizedBox = 'SizedBox';
 
   final String packageName;
   final String widgetsUri;
 
+  final Uri _uriBasic;
   final Uri _uriContainer;
   final Uri _uriFramework;
+  final Uri _uriFoundation;
 
   _Flutter(this.packageName, String uriPrefix)
       : widgetsUri = '$uriPrefix/widgets.dart',
+        _uriBasic = Uri.parse('$uriPrefix/src/widgets/basic.dart'),
         _uriContainer = Uri.parse('$uriPrefix/src/widgets/container.dart'),
-        _uriFramework = Uri.parse('$uriPrefix/src/widgets/framework.dart');
+        _uriFramework = Uri.parse('$uriPrefix/src/widgets/framework.dart'),
+        _uriFoundation = Uri.parse('$uriPrefix/src/foundation/constants.dart');
 
-  bool hasWidgetAsAscendant(ClassElement? element,
-      [Set<ClassElement>? alreadySeen]) {
+  bool hasWidgetAsAscendant(InterfaceElement? element,
+      [Set<InterfaceElement>? alreadySeen]) {
     alreadySeen ??= {};
     if (element == null || !alreadySeen.add(element)) {
       return false;
@@ -95,6 +106,15 @@
       type is InterfaceType &&
       _isExactWidget(type.element, _nameContainer, _uriContainer);
 
+  bool isExactWidgetTypeSizedBox(DartType? type) =>
+      type is InterfaceType &&
+      _isExactWidget(type.element, _nameSizedBox, _uriBasic);
+
+  bool isKDebugMode(Element? element) =>
+      element != null &&
+      element.name == 'kDebugMode' &&
+      element.source?.uri == _uriFoundation;
+
   bool isStatefulWidget(ClassElement? element) {
     if (element == null) {
       return false;
@@ -110,7 +130,7 @@
     return false;
   }
 
-  bool isWidget(ClassElement element) {
+  bool isWidget(InterfaceElement element) {
     if (_isExactWidget(element, _nameWidget, _uriFramework)) {
       return true;
     }
@@ -125,6 +145,6 @@
   bool isWidgetType(DartType? type) =>
       type is InterfaceType && isWidget(type.element);
 
-  bool _isExactWidget(ClassElement element, String type, Uri uri) =>
+  bool _isExactWidget(InterfaceElement element, String type, Uri uri) =>
       element.name == type && element.source.uri == uri;
 }
diff --git a/lib/src/util/leak_detector_visitor.dart b/lib/src/util/leak_detector_visitor.dart
index ef383cc..427c2fc 100644
--- a/lib/src/util/leak_detector_visitor.dart
+++ b/lib/src/util/leak_detector_visitor.dart
@@ -10,19 +10,19 @@
 
 import '../analyzer.dart';
 import '../ast.dart';
-import '../util/dart_type_utilities.dart';
+import '../extensions.dart';
 
 _Predicate _hasConstructorFieldInitializers(
         VariableDeclaration v) =>
     (AstNode n) =>
         n is ConstructorFieldInitializer &&
-        n.fieldName.staticElement == v.name.staticElement;
+        n.fieldName.staticElement == v.declaredElement;
 
 _Predicate _hasFieldFormalParameter(VariableDeclaration v) => (AstNode n) {
       if (n is FieldFormalParameter) {
-        var staticElement = n.identifier.staticElement;
+        var staticElement = n.declaredElement;
         return staticElement is FieldFormalParameterElement &&
-            staticElement.field == v.name.staticElement;
+            staticElement.field == v.declaredElement;
       }
       return false;
     };
@@ -31,7 +31,7 @@
       if (n is ReturnStatement) {
         var expression = n.expression;
         if (expression is SimpleIdentifier) {
-          return expression.staticElement == v.name.staticElement;
+          return expression.staticElement == v.declaredElement;
         }
       }
       return false;
@@ -54,7 +54,9 @@
         return;
       }
 
-      var containerNodes = DartTypeUtilities.traverseNodesInDFS(container);
+      // todo(pq): migrate away from `traverseNodesInDFS` (https://github.com/dart-lang/linter/issues/3745)
+      // ignore: deprecated_member_use_from_same_package
+      var containerNodes = container.traverseNodesInDFS();
 
       var validators = <Iterable<AstNode>>[];
       for (var f in predicateBuilders) {
@@ -86,7 +88,7 @@
   return prefixedIdentifiers.where((n) {
     var declaredElement = variable.declaredElement;
     return declaredElement != null &&
-        n.prefix.staticElement == variable.name.staticElement &&
+        n.prefix.staticElement == variable.declaredElement &&
         _hasMatch(predicates, declaredElement.type, n.identifier.token.lexeme);
   });
 }
@@ -97,7 +99,7 @@
   return prefixedIdentifiers.where((n) => n.argumentList.arguments
       .whereType<SimpleIdentifier>()
       .map((e) => e.staticElement)
-      .contains(variable.name.staticElement));
+      .contains(variable.declaredElement));
 }
 
 Iterable<AstNode> _findNodesInvokingMethodOnVariable(
@@ -111,6 +113,8 @@
           ((_hasMatch(predicates, declaredElement.type, n.methodName.name) &&
                   (_isSimpleIdentifierElementEqualToVariable(
                           n.realTarget, variable) ||
+                      _isPostfixExpressionOperandEqualToVariable(
+                          n.realTarget, variable) ||
                       _isPropertyAccessThroughThis(n.realTarget, variable) ||
                       (n.thisOrAncestorMatching((a) => a == variable) !=
                           null))) ||
@@ -131,7 +135,7 @@
           // Assignment to VariableDeclaration as setter.
           (n.leftHandSide is PropertyAccess &&
               (n.leftHandSide as PropertyAccess).propertyName.token.lexeme ==
-                  variable.name.token.lexeme))
+                  variable.name.lexeme))
       // Being assigned another reference.
       &&
       n.rightHandSide is SimpleIdentifier);
@@ -188,6 +192,16 @@
     n is SimpleIdentifier &&
     _isElementEqualToVariable(n.staticElement, variable);
 
+bool _isPostfixExpressionOperandEqualToVariable(
+    AstNode? n, VariableDeclaration variable) {
+  if (n is PostfixExpression) {
+    var operand = n.operand;
+    return operand is SimpleIdentifier &&
+        _isElementEqualToVariable(operand.staticElement, variable);
+  }
+  return false;
+}
+
 typedef DartTypePredicate = bool Function(DartType type);
 
 typedef _Predicate = bool Function(AstNode node);
diff --git a/lib/src/util/score_utils.dart b/lib/src/util/score_utils.dart
index dd8ac9c..35e614b 100644
--- a/lib/src/util/score_utils.dart
+++ b/lib/src/util/score_utils.dart
@@ -5,28 +5,12 @@
 import 'package:analyzer/src/lint/config.dart'; // ignore: implementation_imports
 import 'package:http/http.dart' as http;
 
-Future<List<String>> _readPedanticLints() async {
-  var version = await pedanticLatestVersion;
-  return _fetchLints(
-      'https://raw.githubusercontent.com/dart-lang/pedantic/master/lib/analysis_options.$version.yaml');
-}
-
 Future<List<String>> _readCoreLints() async => _fetchLints(
     'https://raw.githubusercontent.com/dart-lang/lints/main/lib/core.yaml');
 
-Future<String> get pedanticLatestVersion async {
-  var url =
-      'https://raw.githubusercontent.com/dart-lang/pedantic/master/lib/analysis_options.yaml';
-  var client = http.Client();
-  print('loading $url...');
-  var req = await client.get(Uri.parse(url));
-  var parts = req.body.split('package:pedantic/analysis_options.');
-  return parts[1].split('.yaml')[0];
-}
-
 /// todo(pq): de-duplicate these fetches / URIs
 Future<List<String>> _readFlutterLints() async => _fetchLints(
-    'https://raw.githubusercontent.com/flutter/packages/master/packages/flutter_lints/lib/flutter.yaml');
+    'https://raw.githubusercontent.com/flutter/packages/main/packages/flutter_lints/lib/flutter.yaml');
 
 Future<List<String>> _readRecommendedLints() async => _fetchLints(
     'https://raw.githubusercontent.com/dart-lang/lints/main/lib/recommended.yaml');
@@ -46,14 +30,10 @@
   return lintConfigs.ruleConfigs.map((c) => c.name ?? '<unknown>').toList();
 }
 
-List<String>? _pedanticRules;
 List<String>? _coreRules;
 List<String>? _recommendedRules;
 List<String>? _flutterRules;
 
-Future<List<String>> get pedanticRules async =>
-    _pedanticRules ??= await _readPedanticLints();
-
 Future<List<String>> get coreRules async =>
     _coreRules ??= await _readCoreLints();
 
diff --git a/lib/src/util/tested_expressions.dart b/lib/src/util/tested_expressions.dart
index 41fa780..2b39f78 100644
--- a/lib/src/util/tested_expressions.dart
+++ b/lib/src/util/tested_expressions.dart
@@ -100,7 +100,7 @@
     }
 
     _contradictions = _findContradictoryComparisons(
-        LinkedHashSet.from(facts),
+        LinkedHashSet.of(facts),
         binaryExpression != null
             ? binaryExpression.operator.type
             : TokenType.AMPERSAND_AMPERSAND);
@@ -200,7 +200,7 @@
         }
 
         var set = _findContradictoryComparisons(
-            HashSet.from([ex.leftOperand, ex.rightOperand]), ex.operator.type);
+            HashSet.of([ex.leftOperand, ex.rightOperand]), ex.operator.type);
         expressions.addAll(set);
       };
 }
diff --git a/lib/src/util/unrelated_types_visitor.dart b/lib/src/util/unrelated_types_visitor.dart
index 477342c..98e8159 100644
--- a/lib/src/util/unrelated_types_visitor.dart
+++ b/lib/src/util/unrelated_types_visitor.dart
@@ -4,118 +4,225 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:collection/collection.dart' show IterableExtension;
+import 'package:analyzer/dart/element/type_provider.dart';
 
 import '../analyzer.dart';
 import '../util/dart_type_utilities.dart';
 
-typedef _InterfaceTypePredicate = bool Function(InterfaceType type);
-
-/// Returns a predicate which returns whether a given [InterfaceTypeDefinition]
-/// is equal to [definition].
-_InterfaceTypePredicate _buildImplementsDefinitionPredicate(
-        InterfaceTypeDefinition definition) =>
-    (InterfaceType interface) =>
-        interface.element.name == definition.name &&
-        interface.element.library.name == definition.library;
-
-/// Returns the first type argument on [definition], as implemented by [type].
-///
-/// In the simplest case, [type] is the same class as [definition]. For
-/// example, given the definition `List<E>` and the type `List<int>`,
-/// this function returns the DartType for `int`.
-///
-/// In a more complicated case, we must traverse [type]'s interfaces to find
-/// [definition]. For example, given the definition `Set<E>` and the type `A`
-/// where `A implements B<List, String>` and `B<E, F> implements Set<F>, C<E>`,
-/// this function returns the DartType for `String`.
-DartType? _findIterableTypeArgument(
-    InterfaceTypeDefinition definition, InterfaceType? type,
-    {List<InterfaceType> accumulator = const []}) {
-  if (type == null ||
-      type.isDartCoreObject ||
-      type.isDynamic ||
-      accumulator.contains(type)) {
-    return null;
-  }
-
-  var predicate = _buildImplementsDefinitionPredicate(definition);
-  if (predicate(type)) {
-    return type.typeArguments.first;
-  }
-
-  var implementedInterfaces = type.allSupertypes;
-  var interface = implementedInterfaces.firstWhereOrNull(predicate);
-  if (interface != null && interface.typeArguments.isNotEmpty) {
-    return interface.typeArguments.first;
-  }
-
-  return _findIterableTypeArgument(definition, type.superclass,
-      accumulator: [type, ...accumulator, ...implementedInterfaces]);
-}
-
-bool _isParameterizedMethodInvocation(
-        String methodName, MethodInvocation node) =>
-    node.methodName.name == methodName &&
-    node.argumentList.arguments.length == 1;
-
 /// Base class for visitor used in rules where we want to lint about invoking
 /// methods on generic classes where the type of the singular argument is
 /// unrelated to the singular type argument of the class. Extending this
-/// visitor is as simple as knowing the method, class and library that uniquely
-/// define the target, i.e. implement only [definition] and [methodName].
+/// visitor is as simple as knowing the methods, classes and libraries that
+/// uniquely define the target, i.e. implement only [methods].
 abstract class UnrelatedTypesProcessors extends SimpleAstVisitor<void> {
   final LintRule rule;
   final TypeSystem typeSystem;
+  final TypeProvider typeProvider;
 
-  UnrelatedTypesProcessors(this.rule, this.typeSystem);
+  UnrelatedTypesProcessors(this.rule, this.typeSystem, this.typeProvider);
 
-  /// The type definition which this [UnrelatedTypesProcessors] is concerned
+  /// The method definitions which this [UnrelatedTypesProcessors] is concerned
   /// with.
-  InterfaceTypeDefinition get definition;
+  List<MethodDefinition> get methods;
 
-  /// The name of the method which this [UnrelatedTypesProcessors] is concerned
-  /// with.
-  String get methodName;
+  List<MethodDefinition> get indexOperators => [];
+
+  @override
+  void visitIndexExpression(IndexExpression node) {
+    var matchingMethods =
+        indexOperators.where((method) => '[]' == method.methodName);
+    if (matchingMethods.isEmpty) {
+      return;
+    }
+
+    var targetType = _getTargetType(node, node.realTarget);
+    if (targetType is! InterfaceType) {
+      return;
+    }
+
+    for (var methodDefinition in matchingMethods) {
+      var collectionType = methodDefinition.collectionTypeFor(targetType);
+      if (collectionType != null) {
+        _checkMethod(node.index, methodDefinition, collectionType);
+        return;
+      }
+    }
+  }
 
   @override
   void visitMethodInvocation(MethodInvocation node) {
-    if (!_isParameterizedMethodInvocation(methodName, node)) {
+    if (node.argumentList.arguments.length != 1) {
+      return;
+    }
+
+    var matchingMethods =
+        methods.where((method) => node.methodName.name == method.methodName);
+    if (matchingMethods.isEmpty) {
       return;
     }
 
     // At this point, we know that [node] is an invocation of a method which
-    // has the same name as the method that this UnrelatedTypesProcessors] is
-    // concerned with, and that the method has a single parameter.
+    // has the same name as the method that this [UnrelatedTypesProcessors] is
+    // concerned with, and that the method call has a single argument.
     //
     // We've completed the "cheap" checks, and must now continue with the
     // arduous task of determining whether the method target implements
     // [definition].
+    var targetType = _getTargetType(node, node.realTarget);
+    if (targetType is! InterfaceType) {
+      return;
+    }
 
-    DartType? targetType;
-    var target = node.target;
-    if (target != null) {
-      targetType = target.staticType;
-    } else {
-      var classDeclaration =
-          node.thisOrAncestorOfType<ClassOrMixinDeclaration>();
-      if (classDeclaration == null) {
-        targetType = null;
-      } else if (classDeclaration is ClassDeclaration) {
-        targetType = classDeclaration.declaredElement?.thisType;
-      } else if (classDeclaration is MixinDeclaration) {
-        targetType = classDeclaration.declaredElement?.thisType;
+    for (var methodDefinition in matchingMethods) {
+      var collectionType = methodDefinition.collectionTypeFor(targetType);
+      if (collectionType != null) {
+        _checkMethod(node.argumentList.arguments.first, methodDefinition,
+            collectionType);
+        return;
       }
     }
-    var argument = node.argumentList.arguments.first;
+  }
 
+  DartType? _getTargetType(Expression node, Expression? target) {
+    if (target != null) {
+      return target.staticType;
+    }
+
+    // Look for an implicit receiver, starting with [node]'s parent's parent.
+    for (AstNode? parent = node.parent?.parent;
+        parent != null;
+        parent = parent.parent) {
+      if (parent is ClassDeclaration) {
+        return parent.declaredElement?.thisType;
+      } else if (parent is MixinDeclaration) {
+        return parent.declaredElement?.thisType;
+      } else if (parent is EnumDeclaration) {
+        return parent.declaredElement?.thisType;
+      } else if (parent is ExtensionDeclaration) {
+        return parent.extendedType.type;
+      }
+    }
+    return null;
+  }
+
+  /// Checks a [MethodInvocation] or [IndexExpression] which has a singular
+  /// [argument] and matches [methodDefinition], with a target with a static
+  /// type of [collectionType].
+  void _checkMethod(Expression argument, MethodDefinition methodDefinition,
+      InterfaceType collectionType) {
     // Finally, determine whether the type of the argument is related to the
     // type of the method target.
-    if (targetType is InterfaceType &&
-        DartTypeUtilities.unrelatedTypes(typeSystem, argument.staticType,
-            _findIterableTypeArgument(definition, targetType))) {
-      rule.reportLint(node);
+    var argumentType = argument.staticType;
+    if (argumentType == null) return;
+
+    switch (methodDefinition.expectedArgumentKind) {
+      case ExpectedArgumentKind.assignableToCollectionTypeArgument:
+        var typeArgument =
+            collectionType.typeArguments[methodDefinition.typeArgumentIndex];
+        if (typesAreUnrelated(typeSystem, argumentType, typeArgument)) {
+          rule.reportLint(argument, arguments: [
+            argumentType.getDisplayString(withNullability: true),
+            typeArgument.getDisplayString(withNullability: true),
+          ]);
+        }
+        break;
+
+      case ExpectedArgumentKind.assignableToCollection:
+        if (!typeSystem.isAssignableTo(argumentType, collectionType)) {
+          rule.reportLint(argument, arguments: [
+            argumentType.getDisplayString(withNullability: true),
+            collectionType.getDisplayString(withNullability: true),
+          ]);
+        }
+        break;
+
+      case ExpectedArgumentKind.assignableToIterableOfTypeArgument:
+        var iterableType =
+            collectionType.asInstanceOf(typeProvider.iterableElement);
+        if (iterableType != null &&
+            !typeSystem.isAssignableTo(argumentType, iterableType)) {
+          rule.reportLint(argument, arguments: [
+            argumentType.getDisplayString(withNullability: true),
+            iterableType.getDisplayString(withNullability: true),
+          ]);
+        }
     }
   }
 }
+
+/// A definition of a method and the expected characteristics of the first
+/// argument to any invocation.
+abstract class MethodDefinition {
+  final String methodName;
+
+  /// The index of the type argument which the method argument should match.
+  final int typeArgumentIndex;
+
+  final ExpectedArgumentKind expectedArgumentKind;
+
+  MethodDefinition(
+    this.methodName,
+    this.expectedArgumentKind, {
+    this.typeArgumentIndex = 0,
+  });
+
+  InterfaceType? collectionTypeFor(InterfaceType targetType);
+}
+
+class MethodDefinitionForElement extends MethodDefinition {
+  /// The element on which this method is declared.
+  final ClassElement element;
+
+  MethodDefinitionForElement(
+    this.element,
+    super.methodName,
+    super.expectedArgumentKind, {
+    super.typeArgumentIndex = 0,
+  });
+
+  @override
+  InterfaceType? collectionTypeFor(InterfaceType targetType) =>
+      targetType.asInstanceOf(element);
+}
+
+class MethodDefinitionForName extends MethodDefinition {
+  final String libraryName;
+
+  final String interfaceName;
+
+  MethodDefinitionForName(
+    this.libraryName,
+    this.interfaceName,
+    super.methodName,
+    super.expectedArgumentKind, {
+    super.typeArgumentIndex = 0,
+  });
+
+  @override
+  InterfaceType? collectionTypeFor(InterfaceType targetType) {
+    for (var supertype in [targetType, ...targetType.allSupertypes]) {
+      var element = supertype.element;
+      if (element.name == interfaceName &&
+          element.library.name == libraryName) {
+        return targetType.asInstanceOf(element);
+      }
+    }
+    return null;
+  }
+}
+
+/// The kind of the expected argument.
+enum ExpectedArgumentKind {
+  /// An argument is expected to be assignable to a type argument on the
+  /// collection type.
+  assignableToCollectionTypeArgument,
+
+  /// An argument is expected to be assignable to the collection type.
+  assignableToCollection,
+
+  /// An argument is expected to be assignable to `Iterable<E>` where `E` is the
+  /// (only) type argument on the collection type.
+  assignableToIterableOfTypeArgument,
+}
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 69da127..5f6a853 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -2,25 +2,54 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-final _identifier = RegExp(r'^([(_|$)a-zA-Z]+([_a-zA-Z0-9])*)$');
+import 'ast.dart';
 
-final _lowerCamelCase = RegExp(
-    r'^(_)*[?$a-z][a-z0-9?$]*(([A-Z][a-z0-9?$]*)|([_][0-9][a-z0-9?$]*))*_?$');
+// An identifier here is defined as:
+// * A sequence of `_`, `$`, letters or digits,
+// * where no `$` comes after a digit.
+final _identifier = RegExp(r'^[_$a-z]+(\d[_a-z\d]*)?$', caseSensitive: false);
 
-final _lowerCaseUnderScore = RegExp(r'^([a-z]+([_]?[a-z0-9]+)*)+$');
+// A lower camel-case here is defined as:
+// * Any number of optional leading underscores,
+// * a lower case letter, `$` or `?` followed by a "word-tail"
+//   (a sequence of lower-case letters, digits, `$` or `?`),
+// * followed by any number of either
+//     * an upper case letter followed by a word tail, or
+//     * an underscore and then a digit followed by a word tail.
+// * and potentially ended by a single optional underscore.
+final _lowerCamelCase =
+    RegExp(r'^_*[?$a-z][a-z\d?$]*(?:(?:[A-Z]|_\d)[a-z\d?$]*)*_?$');
+
+// A lower-case underscore (snake-case) is here defined as:
+// * A sequence of lower-case letters, digits and underscores,
+// * starting with a lower-case letter, and
+// * with no two adjacent underscores,
+// * and not ending in an underscore.
+final _lowerCaseUnderScore = RegExp(r'^[a-z](?:_?[a-z\d])*$');
 
 @Deprecated('Prefer: ascii_utils.isValidFileName')
 final _lowerCaseUnderScoreWithDots =
-    RegExp(r'^(_)?[_a-z0-9]*(\.[a-z][_a-z0-9]*)*$');
+    RegExp(r'^_?[_a-z\d]*(?:\.[a-z][_a-z\d]*)*$');
 
+// A lower-case underscored (snake-case) with leading underscores is defined as
+// * An optional leading sequence of any number of underscores,
+// * followed by a sequence of lower-case letters, digits and underscores,
+// * with no two adjacent underscores,
+// * and not ending in an underscore.
 final _lowerCaseUnderScoreWithLeadingUnderscores =
-    RegExp(r'^(_)*([a-z]+([_]?[a-z0-9]+)*)+$');
+    RegExp(r'^_*[a-z](?:_?[a-z\d])*$');
 
-final _pubspec = RegExp(r'^[_]?pubspec\.yaml$');
+final _pubspec = RegExp(r'^_?pubspec\.yaml$');
 
-final _underscores = RegExp(r'^[_]+$');
+// A library prefix here is defined as:
+// * An optional leading `?`,
+// * then any number of underscores, `_`,
+// * then a lower-case letter,
+// * followed by any number of lower-case letters, digits and underscores.
+final _validLibraryPrefix = RegExp(r'^\$?_*[a-z][_a-z\d]*$');
 
-final _validLibraryPrefix = RegExp(r'^[\$]?[_]*[a-z]+[_a-z0-9]*$');
+/// Returns `true` if the given [name] has a leading `_`.
+bool hasLeadingUnderscore(String name) => name.startsWith('_');
 
 /// Check if this [string] is formatted in `CamelCase`.
 bool isCamelCase(String string) => CamelCaseString.isCamelCase(string);
@@ -31,9 +60,6 @@
 /// Returns `true` if this [name] is a legal Dart identifier.
 bool isIdentifier(String name) => _identifier.hasMatch(name);
 
-/// Returns `true` of the given [name] is composed only of `_`s.
-bool isJustUnderscores(String name) => _underscores.hasMatch(name);
-
 /// Returns `true` if this [id] is `lowerCamelCase`.
 bool isLowerCamelCase(String id) =>
     id.length == 1 && isUpperCase(id.codeUnitAt(0)) ||
@@ -60,16 +86,27 @@
 
 /// Returns true if this [id] is a valid package name.
 bool isValidPackageName(String id) =>
-    _lowerCaseUnderScoreWithLeadingUnderscores.hasMatch(id) && isIdentifier(id);
+    _lowerCaseUnderScoreWithLeadingUnderscores.hasMatch(id) &&
+    isIdentifier(id) &&
+    !isReservedWord(id);
 
 class CamelCaseString {
   static final _camelCaseMatcher = RegExp(r'[A-Z][a-z]*');
-  static final _camelCaseTester = RegExp(r'^([_$]*)([A-Z?$]+[a-z0-9]*)+$');
+
+  // A camel case string here is defined as:
+  // * An arbitrary number of optional leading `_`s or `$`s,
+  // * followed by an upper-case letter, `$` or `?`,
+  // * followed by any number of letters, digits, `?` or `$`s.
+  //
+  // This ensures that the text contains a `$`, `?` or upper-case letter
+  // before any lower-case letter or digit, and no letters or `?`s before an
+  // `_`.
+  static final _camelCaseTester = RegExp(r'^_*(?:\$+_+)*[$?A-Z][$?a-zA-Z\d]*$');
 
   final String value;
   CamelCaseString(this.value) {
     if (!isCamelCase(value)) {
-      throw ArgumentError('$value is not CamelCase');
+      throw ArgumentError.value(value, 'value', '$value is not CamelCase');
     }
   }
 
@@ -81,5 +118,5 @@
   static bool isCamelCase(String name) => _camelCaseTester.hasMatch(name);
 
   static String _humanize(String camelCase) =>
-      _camelCaseMatcher.allMatches(camelCase).map((m) => m.group(0)).join(' ');
+      _camelCaseMatcher.allMatches(camelCase).map((m) => m[0]).join(' ');
 }
diff --git a/lib/src/version.dart b/lib/src/version.dart
index 7c6fc3f..c778c39 100644
--- a/lib/src/version.dart
+++ b/lib/src/version.dart
@@ -3,4 +3,4 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /// Package version.  Synchronized w/ pubspec.yaml.
-const String version = '1.8.0';
+const String version = '1.31.0';
diff --git a/pubspec.yaml b/pubspec.yaml
index 7b8b4fe..2edf55e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: linter
-version: 1.8.0
+version: 1.31.0
 
 description: >-
   The implementation of the lint rules supported by the analyzer framework.
@@ -9,13 +9,12 @@
 documentation: https://dart-lang.github.io/linter/lints
 
 environment:
-  sdk: '>=2.12.0 <3.0.0'
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
-  analyzer: '>=2.0.0 <3.0.0'
-  args: ^2.0.0
+  analyzer: ^5.2.0
+  args: ^2.1.0
   collection: ^1.15.0
-  glob: ^2.0.0
   http: ^0.13.0
   meta: ^1.3.0
   path: ^1.8.0
@@ -23,15 +22,13 @@
   yaml: ^3.0.0
 
 dev_dependencies:
-  async: any # referenced in tests
   benchmark_harness: ^2.0.0
   cli_util: ^0.3.0
-  fixnum:
-    path: test_data/mock_packages/fixnum
-  github: ^8.0.1
+  github: ^9.0.0
   grinder: ^0.9.0
-  lints: ^1.0.0
-  markdown: ^4.0.0
+  lints: ^2.0.0
+  markdown: ^6.0.0
   matcher: ^0.12.10
   pub_semver: ^2.0.0
   test: ^1.16.1
+  test_reflective_loader: ^0.2.0
diff --git a/test/all.dart b/test/all.dart
index 95ae5f4..9a2a692 100644
--- a/test/all.dart
+++ b/test/all.dart
@@ -5,15 +5,23 @@
 import 'package:analyzer/src/lint/io.dart';
 
 import 'ascii_utils_test.dart' as ascii_utils;
+import 'doc_test.dart' as doc_test;
 import 'engine_test.dart' as engine_test;
 import 'formatter_test.dart' as formatter_test;
 import 'integration_test.dart' as integration_test;
 import 'mocks.dart';
 import 'rule_test.dart' as rule_test;
+import 'rules/all.dart' as reflective_rule_tests;
+import 'unmocked_sdk_rule_test.dart' as unmocked_sdk_rule_test;
 import 'utils_test.dart' as utils_test;
 import 'validate_format_test.dart' as validate_format;
 import 'validate_headers_test.dart' as validate_headers;
 import 'validate_incompatible_rules.dart' as validate_incompatible_rules;
+import 'validate_no_rule_description_references.dart'
+    as validate_no_rule_description_references;
+import 'validate_rule_description_format_test.dart'
+    as validate_rule_description_format;
+import 'verify_reflective_test_suites.dart' as verify_reflective_test_suites;
 import 'version_test.dart' as version_test;
 
 void main() {
@@ -21,13 +29,19 @@
   outSink = MockIOSink();
 
   ascii_utils.main();
+  doc_test.main();
   engine_test.main();
   formatter_test.main();
   integration_test.main();
   rule_test.main();
+  reflective_rule_tests.main();
+  unmocked_sdk_rule_test.main();
   utils_test.main();
   validate_format.main();
   validate_headers.main();
   validate_incompatible_rules.main();
+  validate_no_rule_description_references.main();
+  validate_rule_description_format.main();
+  verify_reflective_test_suites.main();
   version_test.main();
 }
diff --git a/test/ascii_utils_test.dart b/test/ascii_utils_test.dart
index 9b201fe..e9536be 100644
--- a/test/ascii_utils_test.dart
+++ b/test/ascii_utils_test.dart
@@ -23,6 +23,33 @@
       }
     });
   });
+
+  group('isJustUnderscores', () {
+    test(
+      'empty is not',
+      () => expect(''.isJustUnderscores, isFalse),
+    );
+
+    test(
+      'non-underscore is not',
+      () => expect('A'.isJustUnderscores, isFalse),
+    );
+
+    test(
+      'one underscore is',
+      () => expect('_'.isJustUnderscores, isTrue),
+    );
+
+    test(
+      'multiple underscores is',
+      () => expect('___'.isJustUnderscores, isTrue),
+    );
+
+    test(
+      'underscores followed by non-underscores is not',
+      () => expect('__x'.isJustUnderscores, isFalse),
+    );
+  });
 }
 
 final badFileNames = [
diff --git a/test/doc_test.dart b/test/doc_test.dart
new file mode 100644
index 0000000..8854a1d
--- /dev/null
+++ b/test/doc_test.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import '../tool/doc.dart';
+
+void main() {
+  group('doc generation', () {
+    test('fixStatus (sanity)', () async {
+      var fixStatusMap = await fetchFixStatusMap();
+      // Doc generation reads the fix status map to associate fix status
+      // badges with rule documentation.  Here we check one for sanity.
+      // If the file moves or format changes, we'd expect this to fail.
+      expect(fixStatusMap['always_declare_return_types'], 'hasFix');
+    });
+  });
+}
diff --git a/test/engine_test.dart b/test/engine_test.dart
index de0adb7..7cf8873 100644
--- a/test/engine_test.dart
+++ b/test/engine_test.dart
@@ -28,7 +28,7 @@
 void defineLinterEngineTests() {
   group('engine', () {
     group('reporter', () {
-      void _test(
+      void doTest(
           String label, String expected, Function(PrintingReporter r) report) {
         test(label, () {
           String? msg;
@@ -38,9 +38,9 @@
         });
       }
 
-      _test('exception', 'EXCEPTION: LinterException: foo',
+      doTest('exception', 'EXCEPTION: LinterException: foo',
           (r) => r.exception(LinterException('foo')));
-      _test('warn', 'WARN: foo', (r) => r.warn('foo'));
+      doTest('warn', 'WARN: foo', (r) => r.warn('foo'));
     });
 
     group('exceptions', () {
diff --git a/test/formatter_test.dart b/test/formatter_test.dart
index 256b934..a3dd084 100644
--- a/test/formatter_test.dart
+++ b/test/formatter_test.dart
@@ -33,7 +33,7 @@
 
       var code = TestErrorCode('mock_code', 'MSG')..type = type;
 
-      var source = MockSource()..fullName = '/foo/bar/baz.dart';
+      var source = MockSource('/foo/bar/baz.dart');
 
       var error = AnalysisError(source, 10, 3, code);
 
@@ -83,7 +83,7 @@
         ..errorSeverity = ErrorSeverity('MockErrorSeverity', 0, '', '')
         ..type = type;
 
-      var source = MockSource()..fullName = '/foo/bar/baz.dart';
+      var source = MockSource('/foo/bar/baz.dart');
 
       var error = AnalysisError(source, 12, 13, code);
 
diff --git a/test/integration/always_require_non_null_named_parameters.dart b/test/integration/always_require_non_null_named_parameters.dart
index 965daa2..482e5c8 100644
--- a/test/integration/always_require_non_null_named_parameters.dart
+++ b/test/integration/always_require_non_null_named_parameters.dart
@@ -30,8 +30,6 @@
       await cli.runLinter([
         '$integrationTestDir/always_require_non_null_named_parameters',
         '--rules=always_require_non_null_named_parameters',
-        '--packages',
-        '$ruleTestDir/.mock_packages',
       ], LinterOptions());
       expect(
           collectingOut.trim(),
diff --git a/test/integration/avoid_private_typedef_functions.dart b/test/integration/avoid_private_typedef_functions.dart
index 89bad78..f080d0c 100644
--- a/test/integration/avoid_private_typedef_functions.dart
+++ b/test/integration/avoid_private_typedef_functions.dart
@@ -36,8 +36,8 @@
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'lib.dart 11:14 [lint] Avoid private typedef functions.',
-            'part.dart 11:14 [lint] Avoid private typedef functions.',
+            'lib.dart 11:14',
+            'part.dart 11:14',
             '2 files analyzed, 2 issues found',
           ]));
       expect(exitCode, 1);
diff --git a/test/integration/avoid_relative_lib_imports.dart b/test/integration/avoid_relative_lib_imports.dart
index 22877e7..750dc14 100644
--- a/test/integration/avoid_relative_lib_imports.dart
+++ b/test/integration/avoid_relative_lib_imports.dart
@@ -30,8 +30,6 @@
       await cli.runLinter([
         '$integrationTestDir/avoid_relative_lib_imports',
         '--rules=avoid_relative_lib_imports',
-        '--packages',
-        '$integrationTestDir/avoid_relative_lib_imports/_packages'
       ], LinterOptions());
       expect(
           collectingOut.trim(),
diff --git a/test/integration/avoid_renaming_method_parameters.dart b/test/integration/avoid_renaming_method_parameters.dart
index 74a9f36..f24961f 100644
--- a/test/integration/avoid_renaming_method_parameters.dart
+++ b/test/integration/avoid_renaming_method_parameters.dart
@@ -29,20 +29,18 @@
 
     test('lint lib/ sources and non-lib/ sources', () async {
       await cli.run([
-        '--packages',
-        '$integrationTestDir/avoid_renaming_method_parameters/_packages',
         '$integrationTestDir/avoid_renaming_method_parameters',
         '--rules=avoid_renaming_method_parameters'
       ]);
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'a.dart 29:6 [lint] Don\'t rename parameters of overridden methods.',
-            'a.dart 31:12 [lint] Don\'t rename parameters of overridden methods.',
-            'a.dart 32:9 [lint] Don\'t rename parameters of overridden methods.',
-            'a.dart 34:7 [lint] Don\'t rename parameters of overridden methods.',
-            'a.dart 35:6 [lint] Don\'t rename parameters of overridden methods.',
-            'a.dart 36:6 [lint] Don\'t rename parameters of overridden methods.',
+            'a.dart 31:6 [lint]',
+            'a.dart 33:12 [lint]',
+            'a.dart 34:9 [lint]',
+            'a.dart 36:7 [lint]',
+            'a.dart 37:6 [lint]',
+            'a.dart 38:6 [lint]',
             '3 files analyzed, 6 issues found',
           ]));
       expect(exitCode, 1);
diff --git a/test/integration/cancel_subscriptions.dart b/test/integration/cancel_subscriptions.dart
index 0e268ed..53a1082 100644
--- a/test/integration/cancel_subscriptions.dart
+++ b/test/integration/cancel_subscriptions.dart
@@ -35,7 +35,7 @@
           stringContainsInOrder([
             'StreamSubscription _subscriptionA; // LINT',
             'StreamSubscription _subscriptionF; // LINT',
-            '1 file analyzed, 3 issues found, in'
+            '2 files analyzed, 3 issues found, in'
           ]));
       expect(exitCode, 1);
     });
diff --git a/test/integration/depend_on_referenced_packages.dart b/test/integration/depend_on_referenced_packages.dart
index ab25600..4ab3f76 100644
--- a/test/integration/depend_on_referenced_packages.dart
+++ b/test/integration/depend_on_referenced_packages.dart
@@ -12,7 +12,7 @@
 import '../test_constants.dart';
 
 void main() {
-  group('Depend on referenced packages', () {
+  group('depend_on_referenced_packages', () {
     var currentOut = outSink;
     var collectingOut = CollectingSink();
     setUp(() {
@@ -27,8 +27,6 @@
 
     test('lints files under bin', () async {
       await cli.run([
-        '--packages',
-        '$integrationTestDir/depend_on_referenced_packages/_packages',
         '$integrationTestDir/depend_on_referenced_packages/bin',
         '--rules=depend_on_referenced_packages'
       ]);
@@ -36,13 +34,9 @@
       expect(
           output,
           stringContainsInOrder([
-            "Depend on referenced packages.",
             "import 'package:private_dep/private_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "import 'package:transitive_dep/transitive_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "export 'package:private_dep/private_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "export 'package:transitive_dep/transitive_dep.dart'; // LINT",
           ]));
       expect(output, isNot(contains('// OK')));
@@ -51,8 +45,6 @@
 
     test('lints files under lib', () async {
       await cli.run([
-        '--packages',
-        '$integrationTestDir/depend_on_referenced_packages/_packages',
         '$integrationTestDir/depend_on_referenced_packages/lib',
         '--rules=depend_on_referenced_packages'
       ]);
@@ -60,13 +52,9 @@
       expect(
           output,
           stringContainsInOrder([
-            "Depend on referenced packages.",
             "import 'package:private_dep/private_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "import 'package:transitive_dep/transitive_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "export 'package:private_dep/private_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "export 'package:transitive_dep/transitive_dep.dart'; // LINT",
           ]));
       expect(output, isNot(contains('// OK')));
@@ -75,8 +63,6 @@
 
     test('lints files under test', () async {
       await cli.run([
-        '--packages',
-        '$integrationTestDir/depend_on_referenced_packages/_packages',
         '$integrationTestDir/depend_on_referenced_packages/test',
         '--rules=depend_on_referenced_packages'
       ]);
@@ -84,9 +70,7 @@
       expect(
           output,
           stringContainsInOrder([
-            "Depend on referenced packages.",
             "import 'package:transitive_dep/transitive_dep.dart'; // LINT",
-            "Depend on referenced packages.",
             "export 'package:transitive_dep/transitive_dep.dart'; // LINT",
           ]));
       expect(output, isNot(contains('// OK')));
diff --git a/test/integration/directives_ordering.dart b/test/integration/directives_ordering.dart
index ed5f22d..4839fd6 100644
--- a/test/integration/directives_ordering.dart
+++ b/test/integration/directives_ordering.dart
@@ -26,23 +26,16 @@
     });
 
     test('dart_directives_go_first', () async {
-      var packagesFilePath = File('.packages').absolute.path;
       await cli.run([
-        '--packages',
-        packagesFilePath,
         '$integrationTestDir/directives_ordering/dart_directives_go_first',
         '--rules=directives_ordering'
       ]);
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            "Place 'dart:' imports before other imports.",
             "import 'dart:html';  // LINT",
-            "Place 'dart:' imports before other imports.",
             "import 'dart:isolate';  // LINT",
-            "Place 'dart:' exports before other exports.",
             "export 'dart:html';  // LINT",
-            "Place 'dart:' exports before other exports.",
             "export 'dart:isolate';  // LINT",
             '2 files analyzed, 4 issues found, in'
           ]));
@@ -50,23 +43,16 @@
     });
 
     test('package_directives_before_relative', () async {
-      var packagesFilePath = File('.packages').absolute.path;
       await cli.run([
-        '--packages',
-        packagesFilePath,
         '$integrationTestDir/directives_ordering/package_directives_before_relative',
         '--rules=directives_ordering'
       ]);
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            "Place 'package:' imports before relative imports.",
             "import 'package:async/src/async_cache.dart'; // LINT",
-            "Place 'package:' imports before relative imports.",
             "import 'package:yaml/yaml.dart'; // LINT",
-            "Place 'package:' exports before relative exports.",
             "export 'package:async/src/async_cache.dart'; // LINT",
-            "Place 'package:' exports before relative exports.",
             "export 'package:yaml/yaml.dart'; // LINT",
             '3 files analyzed, 4 issues found, in'
           ]));
@@ -74,19 +60,14 @@
     });
 
     test('export_directives_after_import_directives', () async {
-      var packagesFilePath = File('.packages').absolute.path;
       await cli.run([
-        '--packages',
-        packagesFilePath,
         '$integrationTestDir/directives_ordering/export_directives_after_import_directives',
         '--rules=directives_ordering'
       ]);
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'Specify exports in a separate section after all imports.',
             "export 'dummy.dart';  // LINT",
-            'Specify exports in a separate section after all imports.',
             "export 'dummy2.dart';  // LINT",
             '5 files analyzed, 2 issues found, in'
           ]));
@@ -94,39 +75,24 @@
     });
 
     test('sort_directive_sections_alphabetically', () async {
-      var packagesFilePath = File('.packages').absolute.path;
       await cli.run([
-        '--packages',
-        packagesFilePath,
         '$integrationTestDir/directives_ordering/sort_directive_sections_alphabetically',
         '--rules=directives_ordering'
       ]);
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'Sort directive sections alphabetically.',
             "import 'dart:convert'; // LINT",
-            'Sort directive sections alphabetically.',
             "import 'package:collection/collection.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "import 'package:async/async.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "import 'package:linter/src/formatter.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "import 'dummy3.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "import 'dummy2.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "import 'dummy1.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "export 'dart:convert'; // LINT",
-            'Sort directive sections alphabetically.',
             "export 'package:collection/collection.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "export 'package:async/async.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "export 'package:linter/src/formatter.dart'; // LINT",
-            'Sort directive sections alphabetically.',
             "export 'dummy1.dart'; // LINT",
             '5 files analyzed, 12 issues found, in'
           ]));
@@ -134,10 +100,7 @@
     });
 
     test('lint_one_node_no_more_than_once', () async {
-      var packagesFilePath = File('.packages').absolute.path;
       await cli.run([
-        '--packages',
-        packagesFilePath,
         '$integrationTestDir/directives_ordering/lint_one_node_no_more_than_once',
         '--rules=directives_ordering'
       ]);
@@ -152,10 +115,7 @@
     });
 
     test('match_analyzer_organize_directives', () async {
-      var packagesFilePath = File('.packages').absolute.path;
       await cli.run([
-        '--packages',
-        packagesFilePath,
         '$integrationTestDir/directives_ordering/match_analyzer_organize_directives',
         '--rules=directives_ordering'
       ]);
diff --git a/test/integration/eol_at_end_of_file.dart b/test/integration/eol_at_end_of_file.dart
index 7026f7e..620b659 100644
--- a/test/integration/eol_at_end_of_file.dart
+++ b/test/integration/eol_at_end_of_file.dart
@@ -27,8 +27,7 @@
       ], LinterOptions());
       expect(
           collectingOut.trim(), contains('5 files analyzed, 3 issues found'));
-      expect(collectingOut.trim(),
-          contains('Put a single newline at end of file'));
+      expect(collectingOut.trim(), contains('2:2 [lint]'));
     });
   });
 }
diff --git a/test/integration/flutter_style_todos.dart b/test/integration/flutter_style_todos.dart
index 8df322d..e9f6ab5 100644
--- a/test/integration/flutter_style_todos.dart
+++ b/test/integration/flutter_style_todos.dart
@@ -33,17 +33,17 @@
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'a.dart 8:1 [lint] Use Flutter TODO format:',
-            'a.dart 9:1 [lint] Use Flutter TODO format:',
-            'a.dart 10:1 [lint] Use Flutter TODO format:',
-            'a.dart 11:1 [lint] Use Flutter TODO format:',
-            'a.dart 12:1 [lint] Use Flutter TODO format:',
-            'a.dart 13:1 [lint] Use Flutter TODO format:',
-            'a.dart 14:1 [lint] Use Flutter TODO format:',
-            'a.dart 15:1 [lint] Use Flutter TODO format:',
-            'a.dart 16:1 [lint] Use Flutter TODO format:',
-            'a.dart 17:1 [lint] Use Flutter TODO format:',
-            'a.dart 18:1 [lint] Use Flutter TODO format:',
+            'a.dart 8:1',
+            'a.dart 9:1',
+            'a.dart 10:1',
+            'a.dart 11:1',
+            'a.dart 12:1',
+            'a.dart 13:1',
+            'a.dart 14:1',
+            'a.dart 15:1',
+            'a.dart 16:1',
+            'a.dart 17:1',
+            'a.dart 18:1',
             '1 file analyzed, 11 issues found, in'
           ]));
       expect(exitCode, 1);
diff --git a/test/integration/lines_longer_than_80_chars.dart b/test/integration/lines_longer_than_80_chars.dart
index d727ca8..71441ea 100644
--- a/test/integration/lines_longer_than_80_chars.dart
+++ b/test/integration/lines_longer_than_80_chars.dart
@@ -33,10 +33,10 @@
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'a.dart 7:81 [lint] Avoid lines longer than 80 characters.',
-            'a.dart 11:81 [lint] Avoid lines longer than 80 characters.',
-            'a.dart 20:81 [lint] Avoid lines longer than 80 characters.',
-            'a.dart 25:81 [lint] Avoid lines longer than 80 characters.',
+            'a.dart 7:81',
+            'a.dart 11:81',
+            'a.dart 20:81',
+            'a.dart 25:81',
             "a.dart 32:40 [hint] The diagnostic 'lines_longer_than_80_chars' doesn't need to be ignored here because it's already being ignored.",
             "a.dart 32:68 [hint] The diagnostic 'lines_longer_than_80_chars' doesn't need to be ignored here because it's already being ignored.",
             '1 file analyzed, 6 issues found, in'
diff --git a/test/integration/packages_file_test.dart b/test/integration/packages_file_test.dart
deleted file mode 100644
index 538702b..0000000
--- a/test/integration/packages_file_test.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:io';
-
-import 'package:analyzer/src/lint/io.dart';
-import 'package:analyzer/src/lint/linter.dart';
-import 'package:linter/src/analyzer.dart';
-import 'package:linter/src/cli.dart' as cli;
-import 'package:test/test.dart';
-
-import '../mocks.dart';
-import '../test_constants.dart';
-
-void main() {
-  group('p5', () {
-    var currentOut = outSink;
-    var collectingOut = CollectingSink();
-    setUp(() {
-      exitCode = 0;
-      outSink = collectingOut;
-    });
-    tearDown(() {
-      collectingOut.buffer.clear();
-      outSink = currentOut;
-      exitCode = 0;
-    });
-    group('.packages', () {
-      test('basic', () async {
-        // Requires .packages to analyze cleanly.
-        await cli.runLinter([
-          '$integrationTestDir/p5',
-          '--packages',
-          '$integrationTestDir/p5/_packages'
-        ], LinterOptions([]));
-        // Should have 0 issues.
-        expect(exitCode, 0);
-      });
-    });
-  });
-}
diff --git a/test/integration/prefer_const_constructors.dart b/test/integration/prefer_const_constructors.dart
new file mode 100644
index 0000000..362c0c6
--- /dev/null
+++ b/test/integration/prefer_const_constructors.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:analyzer/src/lint/io.dart';
+import 'package:linter/src/analyzer.dart';
+import 'package:linter/src/cli.dart' as cli;
+import 'package:test/test.dart';
+
+import '../mocks.dart';
+import '../test_constants.dart';
+
+void main() {
+  group('prefer_const_constructors', () {
+    var currentOut = outSink;
+    var collectingOut = CollectingSink();
+    setUp(() {
+      exitCode = 0;
+      outSink = collectingOut;
+    });
+    tearDown(() {
+      collectingOut.buffer.clear();
+      outSink = currentOut;
+      exitCode = 0;
+    });
+
+    test('respect deferred imports', () async {
+      await cli.runLinter([
+        '$integrationTestDir/prefer_const_constructors',
+        '--rules=prefer_const_constructors',
+      ], LinterOptions());
+      expect(
+          collectingOut.trim(),
+          stringContainsInOrder(
+              ['var b = B();', '2 files analyzed, 1 issue found']));
+      expect(exitCode, 1);
+    });
+  });
+}
diff --git a/test/integration/prefer_const_constructors_in_immutables.dart b/test/integration/prefer_const_constructors_in_immutables.dart
index 81ae181..3ff1696 100644
--- a/test/integration/prefer_const_constructors_in_immutables.dart
+++ b/test/integration/prefer_const_constructors_in_immutables.dart
@@ -30,8 +30,6 @@
       await cli.runLinter([
         '$integrationTestDir/prefer_const_constructors_in_immutables',
         '--rules=prefer_const_constructors_in_immutables',
-        '--packages',
-        '$ruleTestDir/.mock_packages',
       ], LinterOptions());
       expect(
           collectingOut.trim(),
diff --git a/test/integration/prefer_mixin.dart b/test/integration/prefer_mixin.dart
index 151beff..d10152a 100644
--- a/test/integration/prefer_mixin.dart
+++ b/test/integration/prefer_mixin.dart
@@ -30,8 +30,6 @@
       await cli.runLinter([
         '$integrationTestDir/prefer_mixin',
         '--rules=prefer_mixin',
-        '--packages',
-        '$ruleTestDir/.mock_packages',
       ], LinterOptions());
       expect(
           collectingOut.trim(),
diff --git a/test/integration/public_member_api_docs.dart b/test/integration/public_member_api_docs.dart
index a1eec9c..b64a7a0 100644
--- a/test/integration/public_member_api_docs.dart
+++ b/test/integration/public_member_api_docs.dart
@@ -27,31 +27,31 @@
       expect(
           result.stdout.trim(),
           stringContainsInOrder([
-            'a.dart:7:1 - Document all public members. - public_member_api_docs',
-            'a.dart:9:11 - Document all public members. - public_member_api_docs',
-            'a.dart:10:9 - Document all public members. - public_member_api_docs',
-            'a.dart:14:16 - Document all public members. - public_member_api_docs',
-            'a.dart:22:11 - Document all public members. - public_member_api_docs',
-            'a.dart:26:16 - Document all public members. - public_member_api_docs',
-            'a.dart:29:3 - Document all public members. - public_member_api_docs',
-            'a.dart:30:5 - Document all public members. - public_member_api_docs',
-            'a.dart:32:8 - Document all public members. - public_member_api_docs',
-            'a.dart:34:8 - Document all public members. - public_member_api_docs',
-            'a.dart:42:3 - Document all public members. - public_member_api_docs',
-            'a.dart:44:3 - Document all public members. - public_member_api_docs',
-            'a.dart:52:9 - Document all public members. - public_member_api_docs',
-            'a.dart:60:14 - Document all public members. - public_member_api_docs',
-            'a.dart:66:6 - Document all public members. - public_member_api_docs',
-            'a.dart:68:3 - Document all public members. - public_member_api_docs',
-            'a.dart:87:1 - Document all public members. - public_member_api_docs',
-            'a.dart:92:5 - Document all public members. - public_member_api_docs',
-            'a.dart:96:6 - Document all public members. - public_member_api_docs',
-            'a.dart:111:1 - Document all public members. - public_member_api_docs',
-            'a.dart:112:11 - Document all public members. - public_member_api_docs',
-            'a.dart:119:14 - Document all public members. - public_member_api_docs',
-            'a.dart:132:1 - Document all public members. - public_member_api_docs',
-            'a.dart:134:7 - Document all public members. - public_member_api_docs',
-            'a.dart:135:1 - Document all public members. - public_member_api_docs',
+            'a.dart:7:1',
+            'a.dart:9:11',
+            'a.dart:10:9',
+            'a.dart:14:16',
+            'a.dart:22:11',
+            'a.dart:26:16',
+            'a.dart:29:3',
+            'a.dart:30:5',
+            'a.dart:32:8',
+            'a.dart:34:8',
+            'a.dart:42:3',
+            'a.dart:44:3',
+            'a.dart:52:9',
+            'a.dart:60:14',
+            'a.dart:66:6',
+            'a.dart:68:3',
+            'a.dart:87:1',
+            'a.dart:92:5',
+            'a.dart:96:6',
+            'a.dart:111:1',
+            'a.dart:112:11',
+            'a.dart:119:14',
+            'a.dart:132:1',
+            'a.dart:134:7',
+            'a.dart:135:1',
           ]));
       expect(result.exitCode, 0);
     });
diff --git a/test/integration/file_names.dart b/test/integration/secure_pubspec_urls.dart
similarity index 61%
rename from test/integration/file_names.dart
rename to test/integration/secure_pubspec_urls.dart
index abff387..9327816 100644
--- a/test/integration/file_names.dart
+++ b/test/integration/secure_pubspec_urls.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -12,36 +12,40 @@
 import '../test_constants.dart';
 
 void main() {
-  group('file_names', () {
+  group('secure_pubspec_urls', () {
     var currentOut = outSink;
     var collectingOut = CollectingSink();
+
     setUp(() {
       exitCode = 0;
       outSink = collectingOut;
     });
+
     tearDown(() {
       collectingOut.buffer.clear();
       outSink = currentOut;
       exitCode = 0;
     });
 
-    test('bad', () async {
-      await cli.run(
-          ['$integrationTestDir/file_names/a-b.dart', '--rules=file_names']);
+    test('finds http urls', () async {
+      await cli.run([
+        '$integrationTestDir/secure_pubspec_urls',
+        '--rules=secure_pubspec_urls',
+      ]);
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'a-b.dart 1:1 [lint] Name source files using `lowercase_with_underscores`.'
+            '4:11 [lint]',
+            '6:16 [lint]',
+            '5:13 [lint]',
+            '15:12 [lint]',
+            '20:13 [lint]',
+            '25:10 [lint]',
+            '28:12 [lint]',
+            '32:12 [lint]',
+            '1 file analyzed, 8 issues found',
           ]));
       expect(exitCode, 1);
     });
-
-    test('ok', () async {
-      await cli.run([
-        '$integrationTestDir/file_names/non-strict.css.dart',
-        '--rules=file_names'
-      ]);
-      expect(exitCode, 0);
-    });
   });
 }
diff --git a/test/integration/sort_pub_dependencies.dart b/test/integration/sort_pub_dependencies.dart
index f365f31..672a029 100644
--- a/test/integration/sort_pub_dependencies.dart
+++ b/test/integration/sort_pub_dependencies.dart
@@ -35,9 +35,9 @@
       expect(
           collectingOut.trim(),
           stringContainsInOrder([
-            'pubspec.yaml 19:3 [lint] Sort pub dependencies.',
-            'pubspec.yaml 26:3 [lint] Sort pub dependencies.',
-            'pubspec.yaml 33:3 [lint] Sort pub dependencies.',
+            'pubspec.yaml 19:3',
+            'pubspec.yaml 26:3',
+            'pubspec.yaml 33:3',
             '1 file analyzed, 3 issues found',
           ]));
       expect(exitCode, 1);
diff --git a/test/integration/unnecessary_library_directive.dart b/test/integration/unnecessary_library_directive.dart
new file mode 100644
index 0000000..3e021da
--- /dev/null
+++ b/test/integration/unnecessary_library_directive.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/lint/io.dart';
+import 'package:analyzer/src/lint/linter.dart';
+import 'package:linter/src/analyzer.dart';
+import 'package:linter/src/cli.dart' as cli;
+import 'package:test/test.dart';
+
+import '../mocks.dart';
+import '../test_constants.dart';
+
+void main() {
+  group('unnecessary_library_directive', () {
+    var currentOut = outSink;
+    var collectingOut = CollectingSink();
+    setUp(() => outSink = collectingOut);
+    tearDown(() {
+      collectingOut.buffer.clear();
+      outSink = currentOut;
+    });
+    test('unnecessary_library_directive', () async {
+      await cli.runLinter([
+        '$integrationTestDir/unnecessary_library_directive',
+        '--rules=unnecessary_library_directive',
+      ], LinterOptions());
+      expect(
+        collectingOut.trim(),
+        startsWith('5 files analyzed, 0 issues found'),
+      );
+    });
+  });
+}
diff --git a/test/integration/use_build_context_synchronously.dart b/test/integration/use_build_context_synchronously.dart
index c9fc8c3..71b3c99 100644
--- a/test/integration/use_build_context_synchronously.dart
+++ b/test/integration/use_build_context_synchronously.dart
@@ -24,14 +24,11 @@
     test('mixed_mode', () async {
       await cli.runLinter([
         '$integrationTestDir/use_build_context_synchronously/lib/unmigrated.dart',
-        '--packages',
-        '$ruleTestDir/.mock_packages',
         '--rules=use_build_context_synchronously',
       ], LinterOptions());
       var out = collectingOut.trim();
       expect(out, contains('1 file analyzed, 1 issue found'));
-      expect(out,
-          contains('21:3 [lint] Do not use BuildContexts across async gaps.'));
+      expect(out, contains('22:3'));
     });
   });
 }
diff --git a/test/integration_test.dart b/test/integration_test.dart
index db515e7..f85edab 100644
--- a/test/integration_test.dart
+++ b/test/integration_test.dart
@@ -28,22 +28,25 @@
     as depend_on_referenced_packages;
 import 'integration/directives_ordering.dart' as directives_ordering;
 import 'integration/exhaustive_cases.dart' as exhaustive_cases;
-import 'integration/file_names.dart' as file_names;
 import 'integration/flutter_style_todos.dart' as flutter_style_todos;
 import 'integration/lines_longer_than_80_chars.dart'
     as lines_longer_than_80_chars;
 import 'integration/only_throw_errors.dart' as only_throw_errors;
 import 'integration/overridden_fields.dart' as overridden_fields;
-import 'integration/packages_file_test.dart' as packages_file_test;
 import 'integration/prefer_asserts_in_initializer_lists.dart'
     as prefer_asserts_in_initializer_lists;
+import 'integration/prefer_const_constructors.dart'
+    as prefer_const_constructors;
 import 'integration/prefer_const_constructors_in_immutables.dart'
     as prefer_const_constructors_in_immutables;
 import 'integration/prefer_mixin.dart' as prefer_mixin;
 import 'integration/prefer_relative_imports.dart' as prefer_relative_imports;
 import 'integration/public_member_api_docs.dart' as public_member_api_docs;
+import 'integration/secure_pubspec_urls.dart' as secure_pubspec_urls;
 import 'integration/sort_pub_dependencies.dart' as sort_pub_dependencies;
 import 'integration/unnecessary_lambdas.dart' as unnecessary_lambdas;
+import 'integration/unnecessary_library_directive.dart'
+    as unnecessary_library_directive;
 import 'integration/unnecessary_string_escapes.dart'
     as unnecessary_string_escapes;
 import 'integration/use_build_context_synchronously.dart'
@@ -129,11 +132,7 @@
         outSink = currentOut;
       });
       test('no warnings due to bad canonicalization', () async {
-        var packagesFilePath =
-            File('$integrationTestDir/p4/_packages').absolute.path;
-        await cli.runLinter(
-            ['--packages', packagesFilePath, '$integrationTestDir/p4'],
-            LinterOptions([]));
+        await cli.runLinter(['$integrationTestDir/p4'], LinterOptions([]));
         expect(collectingOut.trim(),
             startsWith('3 files analyzed, 0 issues found, in'));
       });
@@ -182,13 +181,11 @@
     unnecessary_lambdas.main();
     exhaustive_cases.main();
     avoid_web_libraries_in_flutter.main();
-    packages_file_test.main();
     overridden_fields.main();
     close_sinks.main();
     cancel_subscriptions.main();
     depend_on_referenced_packages.main();
     directives_ordering.main();
-    file_names.main();
     flutter_style_todos.main();
     lines_longer_than_80_chars.main();
     only_throw_errors.main();
@@ -198,22 +195,21 @@
     avoid_relative_lib_imports.main();
     prefer_relative_imports.main();
     public_member_api_docs.main();
+    secure_pubspec_urls.main();
     avoid_renaming_method_parameters.main();
     avoid_private_typedef_functions.main();
     sort_pub_dependencies.main();
     unnecessary_string_escapes.main();
     prefer_mixin.main();
     use_build_context_synchronously.main();
+    prefer_const_constructors.main();
+    unnecessary_library_directive.main();
   });
 }
 
 /// Provide the options found in [optionsSource].
-Map<String, YamlNode> _getOptionsFromString(String? optionsSource) {
+Map<String, YamlNode> _getOptionsFromString(String optionsSource) {
   var options = <String, YamlNode>{};
-  if (optionsSource == null) {
-    return options;
-  }
-
   var doc = loadYamlNode(optionsSource);
 
   // Empty options.
@@ -224,22 +220,16 @@
     throw Exception(
         'Bad options file format (expected map, got ${doc.runtimeType})');
   }
-  if (doc is YamlMap) {
-    doc.nodes.forEach((k, YamlNode v) {
-      Object? key;
-      if (k is YamlScalar) {
-        key = k.value;
-      }
-      if (key is! String) {
-        throw Exception('Bad options file format (expected String scope key, '
-            'got ${k.runtimeType})');
-      }
-      if (v is! YamlNode) {
-        throw Exception('Bad options file format (expected Node value, '
-            'got ${v.runtimeType}: `${v.toString()}`)');
-      }
-      options[key] = v;
-    });
-  }
+  doc.nodes.forEach((k, YamlNode v) {
+    Object? key;
+    if (k is YamlScalar) {
+      key = k.value;
+    }
+    if (key is! String) {
+      throw Exception('Bad options file format (expected String scope key, '
+          'got ${k.runtimeType})');
+    }
+    options[key] = v;
+  });
   return options;
 }
diff --git a/test/mocks.dart b/test/mocks.dart
index cd172fa..1fb7c7e 100644
--- a/test/mocks.dart
+++ b/test/mocks.dart
@@ -5,14 +5,10 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/error/error.dart';
-import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/lint/linter.dart';
-import 'package:analyzer/src/lint/pub.dart';
-import 'package:linter/src/analyzer.dart';
 
 class CollectingSink extends MockIOSink {
   final StringBuffer buffer = StringBuffer();
@@ -88,74 +84,7 @@
   void writeln([Object? obj = '']) {}
 }
 
-class MockPubVisitor implements PubspecVisitor {
-  @override
-  void visitPackageAuthor(PSEntry author) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageAuthors(PSNodeList authors) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDependencies(PSDependencyList dependencies) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDependency(PSDependency dependency) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDependencyOverride(PSDependency dependency) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDependencyOverrides(PSDependencyList dependencies) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDescription(PSEntry description) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDevDependencies(PSDependencyList dependencies) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDevDependency(PSDependency dependency) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageDocumentation(PSEntry documentation) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageHomepage(PSEntry homepage) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageName(PSEntry name) {
-    throw Exception();
-  }
-
-  @override
-  void visitPackageVersion(PSEntry version) {
-    throw Exception();
-  }
-}
-
-class MockReporter implements Reporter {
+class MockReporter extends Reporter {
   List<LinterException> exceptions = <LinterException>[];
 
   List<String> warnings = <String>[];
@@ -173,85 +102,20 @@
   }
 }
 
-class MockRule implements LintRule {
+class MockSource extends BasicSource {
   @override
-  late ErrorReporter reporter;
+  final String fullName;
+
+  MockSource(this.fullName) : super(Uri.file(fullName));
 
   @override
-  late String description;
-
-  @override
-  late String details;
-
-  ProjectVisitor? projectVisitor;
-
-  PubspecVisitor? pubspecVisitor;
-  AstVisitor? visitor;
-
-  @override
-  late Group group;
-
-  @override
-  late LintCode lintCode;
-
-  @override
-  late Maturity maturity;
-
-  @override
-  late String name;
-
-  @override
-  int compareTo(LintRule other) => 0;
-
-  @override
-  ProjectVisitor? getProjectVisitor() => projectVisitor;
-
-  @override
-  PubspecVisitor? getPubspecVisitor() => pubspecVisitor;
-
-  @override
-  AstVisitor? getVisitor() => visitor;
-
-  @override
-  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
-
-  @override
-  void reportPubLint(PSNode node) {}
-}
-
-class MockSource implements Source {
-  @override
-  late TimestampedData<String> contents;
-
-  @override
-  late String encoding;
-
-  @override
-  late String fullName;
-
-  @override
-  late bool isInSystemLibrary;
-
-  @override
-  late Source librarySource;
-
-  @override
-  late int modificationStamp;
-
-  @override
-  late String shortName;
-
-  @override
-  late Source source;
-
-  @override
-  late Uri uri;
-
-  @override
-  late UriKind uriKind;
+  TimestampedData<String> get contents => TimestampedData<String>(0, '');
 
   @override
   bool exists() => false;
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
 }
 
 class TestErrorCode extends ErrorCode {
@@ -263,7 +127,7 @@
 
   TestErrorCode(String name, String message)
       : super(
-          message: message,
+          problemMessage: message,
           name: name,
           uniqueName: 'TestErrorCode.$name',
         );
diff --git a/test/rule_test.dart b/test/rule_test.dart
index dc348e5..8bdacf6 100644
--- a/test/rule_test.dart
+++ b/test/rule_test.dart
@@ -4,7 +4,10 @@
 
 import 'dart:io';
 
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/error/error.dart';
+import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/lint/io.dart';
@@ -65,15 +68,6 @@
         }
       }
     });
-    group('format', () {
-      for (var rule in Registry.ruleRegistry.rules) {
-        test('`${rule.name}` description', () {
-          expect(rule.description.endsWith('.'), isTrue,
-              reason:
-                  "Rule description for ${rule.name} should end with a '.'");
-        });
-      }
-    });
   });
 }
 
@@ -185,16 +179,6 @@
 
 /// Test framework sanity.
 void defineSanityTests() {
-  group('reporting', () {
-    // https://github.com/dart-lang/linter/issues/193
-    group('ignore synthetic nodes', () {
-      var path =
-          p.join('test_data', 'integration', 'synthetic', 'synthetic.dart');
-      var file = File(path);
-      testRule('non_constant_identifier_names', file);
-    });
-  });
-
   test('linter version caching', () {
     expect(lint_service.linterVersion, version);
   });
@@ -212,65 +196,21 @@
 }
 
 void testRule(String ruleName, File file,
-    {bool debug = true, String? analysisOptions}) {
+    {bool debug = true,
+    bool failOnErrors = true,
+    bool useMockSdk = true,
+    String? analysisOptions}) {
   test(ruleName, () async {
     if (!file.existsSync()) {
       throw Exception('No rule found defined at: ${file.path}');
     }
 
-    registerLintRules(inTestMode: debug);
-
-    var expected = <AnnotationMatcher>[];
-
-    var lineNumber = 1;
-    for (var line in file.readAsLinesSync()) {
-      var annotation = extractAnnotation(lineNumber, line);
-      if (annotation != null) {
-        expected.add(AnnotationMatcher(annotation));
-      }
-      ++lineNumber;
-    }
-
-    var rule = Registry.ruleRegistry[ruleName];
-    if (rule == null) {
-      fail('rule `$ruleName` is not registered; unable to test.');
-    }
-
-    var driver = buildDriver(rule, file, analysisOptions: analysisOptions);
-
-    var lints = await driver.lintFiles([file]);
-
-    var actual = <Annotation>[];
-    for (var info in lints) {
-      for (var error in info.errors) {
-        if (error.errorCode.type == ErrorType.LINT) {
-          actual.add(Annotation.forError(error, info.lineInfo));
-        }
-      }
-    }
-    actual.sort();
-    try {
-      expect(actual, unorderedMatches(expected));
-      // ignore: avoid_catches_without_on_clauses
-    } catch (_) {
-      if (debug) {
-        // Dump results for debugging purposes.
-
-        // AST
-        var optionsProvider = AnalysisOptionsProvider();
-        var optionMap = optionsProvider.getOptionsFromString(analysisOptions);
-        var optionsImpl = AnalysisOptionsImpl();
-        applyToAnalysisOptions(optionsImpl, optionMap);
-        var featureSet = optionsImpl.contextFeatures;
-        Spelunker(file.absolute.path, featureSet: featureSet).spelunk();
-        print('');
-        // Lints.
-        ResultReporter(lints).write();
-      }
-
-      // Rethrow and fail.
-      rethrow;
-    }
+    var errorInfos = await _getErrorInfos(ruleName, file,
+        useMockSdk: useMockSdk, debug: debug, analysisOptions: analysisOptions);
+    _validateExpectedLints(file, errorInfos,
+        debug: debug,
+        failOnErrors: failOnErrors,
+        analysisOptions: analysisOptions);
   });
 }
 
@@ -286,6 +226,106 @@
   }
 }
 
+Future<Iterable<AnalysisErrorInfo>> _getErrorInfos(String ruleName, File file,
+    {required bool useMockSdk,
+    required bool debug,
+    required String? analysisOptions}) async {
+  registerLintRules(inTestMode: debug);
+  var rule = Registry.ruleRegistry[ruleName];
+  if (rule == null) {
+    fail('rule `$ruleName` is not registered; unable to test.');
+  }
+
+  if (useMockSdk) {
+    var driver = buildDriver(rule, file, analysisOptions: analysisOptions);
+    return await driver.lintFiles([file]);
+  }
+
+  var path = p.normalize(file.absolute.path);
+  var collection = AnalysisContextCollection(
+    includedPaths: [path],
+    resourceProvider: PhysicalResourceProvider.INSTANCE,
+  );
+
+  var context = collection.contexts[0];
+  var options = context.analysisOptions as AnalysisOptionsImpl;
+  options.lintRules = context.analysisOptions.lintRules.toList();
+  options.lintRules.add(rule);
+  options.lint = true;
+
+  var result =
+      await context.currentSession.getResolvedUnit(path) as ResolvedUnitResult;
+  return [
+    AnalysisErrorInfoImpl(
+      result.errors,
+      result.lineInfo,
+    )
+  ];
+}
+
+/// Parse lint annotations in the given [file] and validate that they correspond
+/// with errors in the provided [errorInfos].
+void _validateExpectedLints(File file, Iterable<AnalysisErrorInfo> errorInfos,
+    {bool debug = true, bool failOnErrors = true, String? analysisOptions}) {
+  var expected = <AnnotationMatcher>[];
+
+  var lineNumber = 1;
+  for (var line in file.readAsLinesSync()) {
+    var annotation = extractAnnotation(lineNumber, line);
+    if (annotation != null) {
+      expected.add(AnnotationMatcher(annotation));
+    }
+    ++lineNumber;
+  }
+
+  var actual = <Annotation>[];
+  var errors = <String>[];
+  for (var info in errorInfos) {
+    for (var error in info.errors) {
+      var errorType = error.errorCode.type;
+      if (errorType == ErrorType.LINT) {
+        actual.add(Annotation.forError(error, info.lineInfo));
+      } else if (failOnErrors && errorType.severity == ErrorSeverity.ERROR) {
+        var location = info.lineInfo.getLocation(error.offset);
+        errors.add(
+            '${file.path} ${location.lineNumber}:${location.columnNumber} ${error.message}');
+      }
+    }
+  }
+
+  if (errors.isNotEmpty) {
+    fail(['Unexpected diagnostics:\n', ...errors].join('\n'));
+  }
+
+  actual.sort();
+  try {
+    expect(actual, unorderedMatches(expected));
+    // TODO (asashour): to be removed after fixing
+    // https://github.com/dart-lang/linter/issues/909
+    // ignore: avoid_catches_without_on_clauses
+  } catch (_) {
+    if (debug) {
+      // Dump results for debugging purposes.
+
+      // AST
+      var optionsProvider = AnalysisOptionsProvider();
+      var optionMap = optionsProvider.getOptionsFromString(analysisOptions);
+      var optionsImpl = AnalysisOptionsImpl();
+      applyToAnalysisOptions(optionsImpl, optionMap);
+
+      var features = optionsImpl.contextFeatures;
+
+      FileSpelunker(file.absolute.path, featureSet: features).spelunk();
+      print('');
+      // Lints.
+      ResultReporter(errorInfos).write();
+    }
+
+    // Rethrow and fail.
+    rethrow;
+  }
+}
+
 /// A [LintFilter] that filters no lint.
 class NoFilter implements LintFilter {
   @override
diff --git a/test/rule_test_support.dart b/test/rule_test_support.dart
new file mode 100644
index 0000000..4f20e81
--- /dev/null
+++ b/test/rule_test_support.dart
@@ -0,0 +1,483 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/error/error.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
+import 'package:analyzer/src/test_utilities/mock_packages.dart';
+import 'package:analyzer/src/test_utilities/mock_sdk.dart';
+import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:linter/src/analyzer.dart';
+import 'package:linter/src/rules.dart';
+import 'package:meta/meta.dart';
+import 'package:test/test.dart';
+
+export 'package:analyzer/src/dart/analysis/experiments.dart';
+export 'package:analyzer/src/dart/error/syntactic_errors.dart';
+export 'package:analyzer/src/error/codes.dart';
+export 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
+
+ExpectedError error(ErrorCode code, int offset, int length,
+        {Pattern? messageContains}) =>
+    ExpectedError(code, offset, length, messageContains: messageContains);
+
+typedef DiagnosticMatcher = bool Function(AnalysisError error);
+
+class AnalysisOptionsFileConfig {
+  final List<String> experiments;
+  final List<String> lints;
+
+  AnalysisOptionsFileConfig({
+    this.experiments = const [],
+    this.lints = const [],
+  });
+
+  String toContent() {
+    var buffer = StringBuffer();
+
+    if (experiments.isNotEmpty) {
+      buffer.writeln('analyzer:');
+      buffer.writeln('  enable-experiment:');
+      for (var experiment in experiments) {
+        buffer.writeln('    - $experiment');
+      }
+    }
+
+    buffer.writeln('linter:');
+    buffer.writeln('  rules:');
+    for (var lint in lints) {
+      buffer.writeln('    - $lint');
+    }
+
+    return buffer.toString();
+  }
+}
+
+/// A description of a diagnostic that is expected to be reported.
+class ExpectedDiagnostic {
+  final DiagnosticMatcher diagnosticMatcher;
+
+  /// The offset of the beginning of the diagnostic's region.
+  final int offset;
+
+  /// The offset of the beginning of the diagnostic's region.
+  final int length;
+
+  /// A pattern that should be contained in the diagnostic message or `null` if
+  /// the message contents should not be checked.
+  final Pattern? messageContains;
+
+  /// Initialize a newly created diagnostic description.
+  ExpectedDiagnostic(this.diagnosticMatcher, this.offset, this.length,
+      {this.messageContains});
+
+  /// Return `true` if the [error] matches this description of what it's
+  /// expected to be.
+  bool matches(AnalysisError error) {
+    if (!diagnosticMatcher(error)) return false;
+    if (error.offset != offset) return false;
+    if (error.length != length) return false;
+    if (messageContains != null && !error.message.contains(messageContains!)) {
+      return false;
+    }
+
+    return true;
+  }
+}
+
+class ExpectedError extends ExpectedDiagnostic {
+  final ErrorCode code;
+
+  /// Initialize a newly created error description.
+  ExpectedError(this.code, int offset, int length, {Pattern? messageContains})
+      : super((AnalysisError error) => error.errorCode == code, offset, length,
+            messageContains: messageContains);
+}
+
+class ExpectedLint extends ExpectedDiagnostic {
+  final String lintName;
+
+  /// Initialize a newly created lint description.
+  ExpectedLint(this.lintName, int offset, int length,
+      {Pattern? messageContains})
+      : super((AnalysisError error) => error.errorCode.name == lintName, offset,
+            length,
+            messageContains: messageContains);
+}
+
+abstract class LintRuleTest extends PubPackageResolutionTest {
+  String? get lintRule;
+
+  @override
+  List<String> get _lintRules => [if (lintRule != null) lintRule!];
+
+  /// Assert that the number of diagnostics that have been gathered matches the
+  /// number of [expectedDiagnostics] and that they have the expected error
+  /// descriptions and locations. The order in which the diagnostics were
+  /// gathered is ignored.
+  Future<void> assertDiagnostics(
+      String code, List<ExpectedDiagnostic> expectedDiagnostics) async {
+    addTestFile(code);
+    await resolveTestFile();
+
+    //
+    // Match actual diagnostics to expected diagnostics.
+    //
+    var unmatchedActual = errors.toList();
+    var unmatchedExpected = expectedDiagnostics.toList();
+    var actualIndex = 0;
+    while (actualIndex < unmatchedActual.length) {
+      var matchFound = false;
+      var expectedIndex = 0;
+      while (expectedIndex < unmatchedExpected.length) {
+        if (unmatchedExpected[expectedIndex]
+            .matches(unmatchedActual[actualIndex])) {
+          matchFound = true;
+          unmatchedActual.removeAt(actualIndex);
+          unmatchedExpected.removeAt(expectedIndex);
+          break;
+        }
+        expectedIndex++;
+      }
+      if (!matchFound) {
+        actualIndex++;
+      }
+    }
+    //
+    // Write the results.
+    //
+    var buffer = StringBuffer();
+    if (unmatchedExpected.isNotEmpty) {
+      buffer.writeln('Expected but did not find:');
+      for (var expected in unmatchedExpected) {
+        buffer.write('  ');
+        if (expected is ExpectedError) {
+          buffer.write(expected.code);
+        }
+        if (expected is ExpectedLint) {
+          buffer.write(expected.lintName);
+        }
+        buffer.write(' [');
+        buffer.write(expected.offset);
+        buffer.write(', ');
+        buffer.write(expected.length);
+        buffer.writeln(']');
+      }
+    }
+    if (unmatchedActual.isNotEmpty) {
+      if (buffer.isNotEmpty) {
+        buffer.writeln();
+      }
+      buffer.writeln('Found but did not expect:');
+      for (var actual in unmatchedActual) {
+        buffer.write('  ');
+        buffer.write(actual.errorCode);
+        buffer.write(' [');
+        buffer.write(actual.offset);
+        buffer.write(', ');
+        buffer.write(actual.length);
+        buffer.write(', ');
+        buffer.write(actual.message);
+        buffer.writeln(']');
+      }
+    }
+    if (buffer.isNotEmpty) {
+      errors.sort((first, second) => first.offset.compareTo(second.offset));
+      buffer.writeln();
+      buffer.writeln('To accept the current state, expect:');
+      for (var actual in errors) {
+        late String diagnosticKind;
+        Object? description;
+        if (actual.errorCode is LintCode) {
+          diagnosticKind = 'lint';
+        } else {
+          diagnosticKind = 'error';
+          description = actual.errorCode;
+        }
+        buffer.write('  $diagnosticKind(');
+        if (description != null) {
+          buffer.write(description);
+          buffer.write(', ');
+        }
+        buffer.write(actual.offset);
+        buffer.write(', ');
+        buffer.write(actual.length);
+        buffer.writeln('),');
+      }
+      fail(buffer.toString());
+    }
+  }
+
+  /// Assert that there are no diagnostics in the given [code].
+  Future<void> assertNoDiagnostics(String code) async =>
+      assertDiagnostics(code, const []);
+
+  ExpectedLint lint(int offset, int length, {Pattern? messageContains}) =>
+      ExpectedLint(lintRule!, offset, length, messageContains: messageContains);
+}
+
+class PubPackageResolutionTest extends _ContextResolutionTest {
+  final List<String> _lintRules = const [];
+
+  bool get addFlutterPackageDep => false;
+
+  bool get addJsPackageDep => false;
+
+  bool get addMetaPackageDep => false;
+
+  @override
+  List<String> get collectionIncludedPaths => [workspaceRootPath];
+
+  List<String> get experiments => [];
+
+  /// The path that is not in [workspaceRootPath], contains external packages.
+  String get packagesRootPath => '/packages';
+
+  String get testFileName => 'test.dart';
+
+  @override
+  String get testFilePath => '$testPackageLibPath/$testFileName';
+
+  String? get testPackageLanguageVersion => null;
+
+  String get testPackageLibPath => '$testPackageRootPath/lib';
+
+  String get testPackageRootPath => '$workspaceRootPath/test';
+
+  String get workspaceRootPath => '/home';
+
+  @override
+  @mustCallSuper
+  void setUp() {
+    super.setUp();
+    // Check for any needlessly enabled experiments.
+    for (var experiment in experiments) {
+      var feature = ExperimentStatus.knownFeatures[experiment];
+      if (feature?.isEnabledByDefault ?? false) {
+        fail("The '$experiment' experiment is enabled by default, "
+            'try removing it from `experiments`.');
+      }
+    }
+
+    writeTestPackageAnalysisOptionsFile(
+      AnalysisOptionsFileConfig(
+        experiments: experiments,
+        lints: _lintRules,
+      ),
+    );
+    _writeTestPackageConfig(
+      PackageConfigFileBuilder(),
+    );
+  }
+
+  void writePackageConfig(String path, PackageConfigFileBuilder config) {
+    newFile2(
+      path,
+      config.toContent(
+        toUriStr: toUriStr,
+      ),
+    );
+  }
+
+  void writeTestPackageAnalysisOptionsFile(AnalysisOptionsFileConfig config) {
+    newAnalysisOptionsYamlFile(
+      testPackageRootPath,
+      config.toContent(),
+    );
+  }
+
+  void writeTestPackagePubspecYamlFile(PubspecYamlFileConfig config) {
+    newPubspecYamlFile(testPackageRootPath, config.toContent());
+  }
+
+  void _writeTestPackageConfig(PackageConfigFileBuilder config) {
+    var configCopy = config.copy();
+
+    configCopy.add(
+      name: 'test',
+      rootPath: testPackageRootPath,
+      languageVersion: testPackageLanguageVersion,
+    );
+
+    if (addFlutterPackageDep) {
+      var flutterPath = '/packages/flutter';
+      addFlutterPackageFiles(
+        getFolder(flutterPath),
+      );
+      configCopy.add(name: 'flutter', rootPath: flutterPath);
+    }
+
+    if (addJsPackageDep) {
+      var jsPath = '/packages/js';
+      MockPackages.addJsPackageFiles(
+        getFolder(jsPath),
+      );
+      configCopy.add(name: 'js', rootPath: jsPath);
+    }
+
+    if (addMetaPackageDep) {
+      var metaPath = '/packages/meta';
+      MockPackages.addMetaPackageFiles(
+        getFolder(metaPath),
+      );
+      configCopy.add(name: 'meta', rootPath: metaPath);
+    }
+
+    var path = '$testPackageRootPath/.dart_tool/package_config.json';
+    writePackageConfig(path, configCopy);
+  }
+
+  /// Create a fake 'flutter' package that can be used by tests.
+  static void addFlutterPackageFiles(Folder rootFolder) {
+    var libFolder = rootFolder.getChildAssumingFolder('lib');
+    libFolder.getChildAssumingFile('widgets.dart').writeAsStringSync(r'''
+export 'src/widgets/framework.dart';
+''');
+
+    libFolder
+        .getChildAssumingFolder('src')
+        .getChildAssumingFolder('widgets')
+        .getChildAssumingFile('framework.dart')
+        .writeAsStringSync(r'''   
+abstract class BuildContext {
+  Widget get widget;
+}
+
+class Widget {
+}
+''');
+  }
+}
+
+class PubspecYamlFileConfig {
+  final String? name;
+  final String? sdkVersion;
+  final List<PubspecYamlFileDependency> dependencies;
+
+  PubspecYamlFileConfig({
+    this.name,
+    this.sdkVersion,
+    this.dependencies = const [],
+  });
+
+  String toContent() {
+    var buffer = StringBuffer();
+
+    if (name != null) {
+      buffer.writeln('name: $name');
+    }
+
+    if (sdkVersion != null) {
+      buffer.writeln('environment:');
+      buffer.writeln("  sdk: '$sdkVersion'");
+    }
+
+    if (dependencies.isNotEmpty) {
+      buffer.writeln('dependencies:');
+      for (var dependency in dependencies) {
+        buffer.writeln('  ${dependency.name}: ${dependency.version}');
+      }
+    }
+
+    return buffer.toString();
+  }
+}
+
+class PubspecYamlFileDependency {
+  final String name;
+  final String version;
+
+  PubspecYamlFileDependency({
+    required this.name,
+    this.version = 'any',
+  });
+}
+
+abstract class _ContextResolutionTest with ResourceProviderMixin {
+  static bool _lintRulesAreRegistered = false;
+
+  final ByteStore _byteStore = MemoryByteStore();
+
+  AnalysisContextCollectionImpl? _analysisContextCollection;
+
+  late ResolvedUnitResult result;
+
+  List<String> get collectionIncludedPaths;
+
+  /// The analysis errors that were computed during analysis.
+  List<AnalysisError> get errors => result.errors;
+
+  Folder get sdkRoot => newFolder('/sdk');
+
+  String get testFilePath => '/test/lib/test.dart';
+
+  void addTestFile(String content) {
+    newFile2(testFilePath, content);
+  }
+
+  @override
+  File newFile2(String path, String content) {
+    if (_analysisContextCollection != null && !path.endsWith('.dart')) {
+      throw StateError('Only dart files can be changed after analysis.');
+    }
+
+    return super.newFile(path, content);
+  }
+
+  Future<ResolvedUnitResult> resolveFile(String path) async {
+    var analysisContext = _contextFor(path);
+    var session = analysisContext.currentSession;
+    return await session.getResolvedUnit(path) as ResolvedUnitResult;
+  }
+
+  Future<void> resolveTestFile() => _resolveFile(testFilePath);
+
+  @mustCallSuper
+  void setUp() {
+    if (!_lintRulesAreRegistered) {
+      registerLintRules();
+      _lintRulesAreRegistered = true;
+    }
+
+    createMockSdk(
+      resourceProvider: resourceProvider,
+      root: sdkRoot,
+    );
+  }
+
+  DriverBasedAnalysisContext _contextFor(String path) {
+    _createAnalysisContexts();
+
+    var convertedPath = convertPath(path);
+    return _analysisContextCollection!.contextFor(convertedPath);
+  }
+
+  /// Create all analysis contexts in [collectionIncludedPaths].
+  void _createAnalysisContexts() {
+    if (_analysisContextCollection != null) {
+      return;
+    }
+
+    _analysisContextCollection = AnalysisContextCollectionImpl(
+      byteStore: _byteStore,
+      declaredVariables: {},
+      enableIndex: true,
+      includedPaths: collectionIncludedPaths.map(convertPath).toList(),
+      resourceProvider: resourceProvider,
+      sdkPath: sdkRoot.path,
+    );
+  }
+
+  /// Resolve the file with the [path] into [result].
+  Future<void> _resolveFile(String path) async {
+    var convertedPath = convertPath(path);
+
+    result = await resolveFile(convertedPath);
+  }
+}
diff --git a/test/rules/all.dart b/test/rules/all.dart
new file mode 100644
index 0000000..d15aada
--- /dev/null
+++ b/test/rules/all.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'annotate_overrides_test.dart' as annotate_overrides;
+import 'avoid_annotating_with_dynamic_test.dart'
+    as avoid_annotating_with_dynamic;
+import 'avoid_equals_and_hash_code_on_mutable_classes_test.dart'
+    as avoid_equals_and_hash_code_on_mutable_classes;
+import 'avoid_escaping_inner_quotes_test.dart' as avoid_escaping_inner_quotes;
+import 'avoid_final_parameters_test.dart' as avoid_final_parameters;
+import 'avoid_function_literals_in_foreach_calls_test.dart'
+    as avoid_function_literals_in_foreach_calls;
+import 'avoid_init_to_null_test.dart' as avoid_init_to_null;
+import 'avoid_private_typedef_functions_test.dart'
+    as avoid_private_typedef_functions;
+import 'avoid_redundant_argument_values_test.dart'
+    as avoid_redundant_argument_values;
+import 'avoid_renaming_method_parameters_test.dart'
+    as avoid_renaming_method_parameters;
+import 'avoid_returning_this_test.dart' as avoid_returning_this;
+import 'avoid_setters_without_getters_test.dart'
+    as avoid_setters_without_getters;
+import 'avoid_shadowing_type_parameters_test.dart'
+    as avoid_shadowing_type_parameters;
+import 'avoid_types_as_parameter_names_test.dart'
+    as avoid_types_as_parameter_names;
+import 'avoid_unused_constructor_parameters_test.dart'
+    as avoid_unused_constructor_parameters;
+import 'avoid_void_async_test.dart' as avoid_void_async;
+import 'collection_methods_unrelated_type_test.dart'
+    as collection_methods_unrelated_type;
+import 'conditional_uri_does_not_exist_test.dart'
+    as conditional_uri_does_not_exist;
+import 'constant_identifier_names_test.dart' as constant_identifier_names;
+import 'dangling_library_doc_comments_test.dart'
+    as dangling_library_doc_comments;
+import 'deprecated_consistency_test.dart' as deprecated_consistency;
+import 'discarded_futures_test.dart' as discarded_futures;
+import 'enable_null_safety_test.dart' as enable_null_safety;
+import 'file_names_test.dart' as file_names;
+import 'flutter_style_todos_test.dart' as flutter_style_todos;
+import 'hash_and_equals_test.dart' as hash_and_equals;
+import 'library_annotations_test.dart' as library_annotations;
+import 'library_names_test.dart' as library_names;
+import 'library_private_types_in_public_api_test.dart'
+    as library_private_types_in_public_api;
+import 'literal_only_boolean_expressions_test.dart'
+    as literal_only_boolean_expressions;
+import 'missing_whitespace_between_adjacent_strings_test.dart'
+    as missing_whitespace_between_adjacent_strings;
+import 'non_constant_identifier_names_test.dart'
+    as non_constant_identifier_names;
+import 'null_closures_test.dart' as null_closures;
+import 'omit_local_variable_types_test.dart' as omit_local_variable_types;
+import 'overridden_fields_test.dart' as overridden_fields;
+import 'parameter_assignments_test.dart' as parameter_assignments;
+import 'prefer_asserts_in_initializer_lists_test.dart'
+    as prefer_asserts_in_initializer_lists;
+import 'prefer_collection_literals_test.dart' as prefer_collection_literals;
+import 'prefer_const_constructors_in_immutables_test.dart'
+    as prefer_const_constructors_in_immutables;
+import 'prefer_const_constructors_test.dart' as prefer_const_constructors;
+import 'prefer_const_declarations_test.dart' as prefer_const_declarations;
+import 'prefer_const_literals_to_create_immutables_test.dart'
+    as prefer_const_literals_to_create_immutables;
+import 'prefer_contains_test.dart' as prefer_contains;
+import 'prefer_equal_for_default_values_test.dart'
+    as prefer_equal_for_default_values;
+import 'prefer_final_fields_test.dart' as prefer_final_fields;
+import 'prefer_final_parameters_test.dart' as prefer_final_parameters;
+import 'prefer_generic_function_type_aliases_test.dart'
+    as prefer_generic_function_type_aliases;
+import 'prefer_spread_collections_test.dart' as prefer_spread_collections;
+import 'public_member_api_docs_test.dart' as public_member_api_docs;
+import 'recursive_getters_test.dart' as recursive_getters;
+import 'sort_constructors_first_test.dart' as sort_constructors_first;
+import 'sort_unnamed_constructors_first_test.dart'
+    as sort_unnamed_constructors_first;
+import 'super_goes_last_test.dart' as super_goes_last;
+import 'tighten_type_of_initializing_formals_test.dart'
+    as tighten_type_of_initializing_formals;
+import 'type_init_formals_test.dart' as type_init_formals;
+import 'unawaited_futures_test.dart' as unawaited_futures;
+import 'unnecessary_brace_in_string_interps_test.dart'
+    as unnecessary_brace_in_string_interps;
+import 'unnecessary_const_test.dart' as unnecessary_const;
+import 'unnecessary_null_checks_test.dart' as unnecessary_null_checks;
+import 'unnecessary_overrides_test.dart' as unnecessary_overrides;
+import 'use_build_context_synchronously_test.dart'
+    as use_build_context_synchronously;
+import 'use_enums_test.dart' as use_enums;
+import 'use_is_even_rather_than_modulo_test.dart'
+    as use_is_even_rather_than_modulo;
+import 'use_late_for_private_fields_and_variables_test.dart'
+    as use_late_for_private_fields_and_variables;
+import 'use_super_parameters_test.dart' as use_super_parameters;
+import 'void_checks_test.dart' as void_checks;
+
+void main() {
+  annotate_overrides.main();
+  avoid_annotating_with_dynamic.main();
+  avoid_equals_and_hash_code_on_mutable_classes.main();
+  avoid_escaping_inner_quotes.main();
+  avoid_function_literals_in_foreach_calls.main();
+  avoid_final_parameters.main();
+  avoid_init_to_null.main();
+  avoid_private_typedef_functions.main();
+  avoid_redundant_argument_values.main();
+  avoid_renaming_method_parameters.main();
+  avoid_returning_this.main();
+  avoid_setters_without_getters.main();
+  avoid_shadowing_type_parameters.main();
+  avoid_types_as_parameter_names.main();
+  avoid_unused_constructor_parameters.main();
+  avoid_void_async.main();
+  collection_methods_unrelated_type.main();
+  conditional_uri_does_not_exist.main();
+  constant_identifier_names.main();
+  dangling_library_doc_comments.main();
+  deprecated_consistency.main();
+  discarded_futures.main();
+  enable_null_safety.main();
+  file_names.main();
+  flutter_style_todos.main();
+  hash_and_equals.main();
+  library_annotations.main();
+  library_names.main();
+  library_private_types_in_public_api.main();
+  literal_only_boolean_expressions.main();
+  missing_whitespace_between_adjacent_strings.main();
+  non_constant_identifier_names.main();
+  null_closures.main();
+  omit_local_variable_types.main();
+  overridden_fields.main();
+  parameter_assignments.main();
+  prefer_asserts_in_initializer_lists.main();
+  prefer_collection_literals.main();
+  prefer_const_constructors.main();
+  prefer_const_constructors_in_immutables.main();
+  prefer_const_declarations.main();
+  prefer_const_literals_to_create_immutables.main();
+  prefer_contains.main();
+  prefer_equal_for_default_values.main();
+  prefer_final_fields.main();
+  prefer_final_parameters.main();
+  prefer_generic_function_type_aliases.main();
+  prefer_spread_collections.main();
+  public_member_api_docs.main();
+  recursive_getters.main();
+  sort_constructors_first.main();
+  sort_unnamed_constructors_first.main();
+  super_goes_last.main();
+  tighten_type_of_initializing_formals.main();
+  type_init_formals.main();
+  unawaited_futures.main();
+  unnecessary_brace_in_string_interps.main();
+  unnecessary_const.main();
+  unnecessary_null_checks.main();
+  unnecessary_overrides.main();
+  use_build_context_synchronously.main();
+  use_enums.main();
+  use_is_even_rather_than_modulo.main();
+  use_late_for_private_fields_and_variables.main();
+  use_super_parameters.main();
+  void_checks.main();
+}
diff --git a/test/rules/analysis_options.yaml b/test/rules/analysis_options.yaml
new file mode 100644
index 0000000..f6cc916
--- /dev/null
+++ b/test/rules/analysis_options.yaml
@@ -0,0 +1,6 @@
+include: ../../analysis_options.yaml
+
+linter:
+  rules:
+    # For reflective test naming
+    non_constant_identifier_names: false
diff --git a/test/rules/annotate_overrides_test.dart b/test/rules/annotate_overrides_test.dart
new file mode 100644
index 0000000..8682bf6
--- /dev/null
+++ b/test/rules/annotate_overrides_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AnnotateOverridesTest);
+  });
+}
+
+@reflectiveTest
+class AnnotateOverridesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'annotate_overrides';
+
+  test_field() async {
+    await assertDiagnostics(r'''
+class O {
+  int get x => 0;
+}
+    
+enum A implements O {
+  a,b,c;
+  int get x => 0;
+}
+''', [
+      lint(76, 1),
+    ]);
+  }
+
+  test_method() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  String toString() => '';
+}
+''', [
+      lint(27, 8),
+    ]);
+  }
+
+  test_ok() async {
+    await assertNoDiagnostics(r'''
+class O {
+  int get x => 0;
+}
+    
+enum A implements O {    
+  a,b,c;
+  @override
+  int get x => 0;
+  @override
+  String toString() => '';
+}
+''');
+  }
+}
diff --git a/test/rules/avoid_annotating_with_dynamic_test.dart b/test/rules/avoid_annotating_with_dynamic_test.dart
new file mode 100644
index 0000000..ba6c63a
--- /dev/null
+++ b/test/rules/avoid_annotating_with_dynamic_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidAnnotatingWithDynamicTest);
+  });
+}
+
+@reflectiveTest
+class AvoidAnnotatingWithDynamicTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_annotating_with_dynamic';
+
+  test_fieldFormals() async {
+    await assertDiagnostics(r'''
+class A {
+  var a;
+  A(dynamic this.a);
+}
+''', [
+      lint(23, 14),
+    ]);
+  }
+
+  test_super() async {
+    await assertDiagnostics(r'''
+class A {
+  var a;
+  var b;
+  A(this.a, this.b);
+}
+class B extends A {
+  B(dynamic super.a, dynamic super.b);
+}
+''', [
+      lint(75, 15),
+      lint(92, 15),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_equals_and_hash_code_on_mutable_classes_test.dart b/test/rules/avoid_equals_and_hash_code_on_mutable_classes_test.dart
new file mode 100644
index 0000000..6c249d9
--- /dev/null
+++ b/test/rules/avoid_equals_and_hash_code_on_mutable_classes_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidEqualsAndHashCodeOnMutableClassesTest);
+  });
+}
+
+@reflectiveTest
+class AvoidEqualsAndHashCodeOnMutableClassesTest extends LintRuleTest {
+  @override
+  bool get addMetaPackageDep => true;
+  @override
+  String get lintRule => 'avoid_equals_and_hash_code_on_mutable_classes';
+
+  test_enums() async {
+    await assertDiagnostics(r'''
+enum E {
+  e(1), f(2), g(3);
+  final int key;
+  const E(this.key);
+  bool operator ==(Object other) => other is E && other.key == key;
+  int get hashCode => key.hashCode;
+}
+''', [
+      error(
+          CompileTimeErrorCode.ILLEGAL_CONCRETE_ENUM_MEMBER_DECLARATION, 83, 2),
+      error(CompileTimeErrorCode.ILLEGAL_CONCRETE_ENUM_MEMBER_DECLARATION, 145,
+          8),
+      // No lint.
+    ]);
+  }
+}
diff --git a/test/rules/avoid_escaping_inner_quotes_test.dart b/test/rules/avoid_escaping_inner_quotes_test.dart
new file mode 100644
index 0000000..6eb1f4f
--- /dev/null
+++ b/test/rules/avoid_escaping_inner_quotes_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidEscapingInnerQuotesTest);
+  });
+}
+
+@reflectiveTest
+class AvoidEscapingInnerQuotesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_escaping_inner_quotes';
+
+  test_singleQuotes() async {
+    await assertDiagnostics(r'''
+void f(String d) {
+  print('a\'b\'c ${d.length}');
+}
+''', [
+      lint(27, 21),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_final_parameters_test.dart b/test/rules/avoid_final_parameters_test.dart
new file mode 100644
index 0000000..4df4940
--- /dev/null
+++ b/test/rules/avoid_final_parameters_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidFinalParametersTest);
+  });
+}
+
+@reflectiveTest
+class AvoidFinalParametersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_final_parameters';
+
+  test_super() async {
+    await assertDiagnostics(r'''
+class A {
+  String? a;
+  String? b;
+  A(this.a, this.b);
+}
+class B extends A {
+  B(final super.a, final super.b);
+}
+''', [
+      error(HintCode.UNNECESSARY_FINAL, 83, 5),
+      error(HintCode.UNNECESSARY_FINAL, 98, 5),
+      lint(83, 13),
+      lint(98, 13),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_function_literals_in_foreach_calls_test.dart b/test/rules/avoid_function_literals_in_foreach_calls_test.dart
new file mode 100644
index 0000000..04060c8
--- /dev/null
+++ b/test/rules/avoid_function_literals_in_foreach_calls_test.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidFunctionLiteralsInForeachCalls);
+  });
+}
+
+@reflectiveTest
+class AvoidFunctionLiteralsInForeachCalls extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_function_literals_in_foreach_calls';
+
+  test_expectedIdentifier() async {
+    await assertDiagnostics(r'''
+void f(dynamic iter) => iter?.forEach(...);
+''', [
+      // No lint
+      error(ParserErrorCode.MISSING_IDENTIFIER, 38, 3),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_init_to_null_test.dart b/test/rules/avoid_init_to_null_test.dart
new file mode 100644
index 0000000..b401fc0
--- /dev/null
+++ b/test/rules/avoid_init_to_null_test.dart
@@ -0,0 +1,117 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidInitToNullTest);
+    defineReflectiveTests(AvoidInitToNullSuperFormalsTest);
+  });
+}
+
+@reflectiveTest
+class AvoidInitToNullSuperFormalsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_init_to_null';
+
+  test_nullableStringInit() async {
+    await assertDiagnostics(r'''
+class A {
+  String? a;
+  A({this.a = null});
+}
+''', [
+      lint(28, 13),
+    ]);
+  }
+
+  test_superInit_2() async {
+    await assertDiagnostics(r'''
+class A {
+  String? a;
+  A({this.a = null});
+}
+class B extends A {
+  B({super.a = null});
+}
+''', [
+      lint(28, 13),
+      lint(72, 14),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3349
+  test_superInit_nolint() async {
+    await assertNoDiagnostics(r'''
+class A {
+  String? a;
+  A({this.a = ''});
+}
+
+class B extends A {
+  B({super.a = null});
+}
+''');
+  }
+}
+
+@reflectiveTest
+class AvoidInitToNullTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_init_to_null';
+
+  test_invalidAssignment_field() async {
+    await assertDiagnostics(r'''
+class X {
+  int x = null;
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.INVALID_ASSIGNMENT, 20, 4),
+    ]);
+  }
+
+  test_invalidAssignment_namedParameter() async {
+    await assertDiagnostics(r'''
+class X {
+  X({int a: null});
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.INVALID_ASSIGNMENT, 22, 4),
+    ]);
+  }
+
+  test_invalidAssignment_namedParameter_fieldFormal() async {
+    await assertDiagnostics(r'''
+class X {
+  int x;
+  X({this.x: null});
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.INVALID_ASSIGNMENT, 32, 4),
+    ]);
+  }
+
+  test_invalidAssignment_topLevelVariable() async {
+    await assertDiagnostics(r'''
+int i = null;
+''', [
+      // No lint
+      error(CompileTimeErrorCode.INVALID_ASSIGNMENT, 8, 4),
+    ]);
+  }
+
+  test_nullable_topLevelVariable() async {
+    await assertDiagnostics(r'''
+int? ii = null;
+''', [
+      lint(5, 9),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_private_typedef_functions_test.dart b/test/rules/avoid_private_typedef_functions_test.dart
new file mode 100644
index 0000000..7c3fc73
--- /dev/null
+++ b/test/rules/avoid_private_typedef_functions_test.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidPrivateTypedefFunctionsTest);
+  });
+}
+
+@reflectiveTest
+class AvoidPrivateTypedefFunctionsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_private_typedef_functions';
+
+  test_nonFunctionTypeAlias() async {
+    await assertNoDiagnostics(r'''
+// ignore: unused_element
+typedef _td = List<String>;
+''');
+  }
+}
diff --git a/test/rules/avoid_redundant_argument_values_test.dart b/test/rules/avoid_redundant_argument_values_test.dart
new file mode 100644
index 0000000..d628c95
--- /dev/null
+++ b/test/rules/avoid_redundant_argument_values_test.dart
@@ -0,0 +1,298 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidRedundantArgumentValuesTest);
+    defineReflectiveTests(AvoidRedundantArgumentValuesNamedArgsAnywhereTest);
+  });
+}
+
+@reflectiveTest
+class AvoidRedundantArgumentValuesNamedArgsAnywhereTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_redundant_argument_values';
+
+  test_namedArgumentBeforePositional() async {
+    await assertDiagnostics(r'''
+void foo(int a, int b, {bool c = true}) {}
+
+void f() {
+  foo(0, c: true, 1);
+}
+''', [
+      lint(67, 4),
+    ]);
+  }
+}
+
+@reflectiveTest
+class AvoidRedundantArgumentValuesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_redundant_argument_values';
+
+  /// https://github.com/dart-lang/linter/issues/3617
+  test_enumDeclaration() async {
+    await assertDiagnostics(r'''
+enum TestEnum {
+  a(test: false);
+
+  const TestEnum({this.test = false});
+
+  final bool test;
+}
+''', [
+      lint(26, 5),
+    ]);
+  }
+
+  @FailingTest(issue: 'https://github.com/dart-lang/linter/issues/3447')
+  test_fromEnvironment() async {
+    await assertNoDiagnostics(r'''
+const bool someDefine = bool.fromEnvironment('someDefine');
+
+void f({bool test = true}) {}
+
+void g() {
+  f(
+    test: !someDefine,
+  );
+} 
+''');
+  }
+
+  /// https://github.com/dart-lang/sdk/issues/49596
+  test_legacyRequired() async {
+    var a = newFile('$testPackageLibPath/a.dart', r'''
+class Foo {
+  int? foo;
+  Foo({required this.foo});
+}
+''');
+    await resolveFile(a.path);
+
+    await assertNoDiagnostics(r'''
+// @dart = 2.9
+import 'a.dart';
+
+void f() {
+  Foo(foo: null);
+}
+''');
+  }
+
+  test_redirectingFactoryConstructor() async {
+    await assertNoDiagnostics(r'''
+class A {
+  factory A([int? value]) = B;
+  A._();
+}
+class B extends A {
+  B([int? value = 2]) : super._();
+}
+void f() {
+  A();
+  A(null);
+  A(1);
+}
+''');
+  }
+
+  test_redirectingFactoryConstructor_multipleOptional() async {
+    await assertNoDiagnostics(r'''
+class A {
+  factory A([int? one, int? two]) = B;
+  A._();
+}
+class B extends A {
+  int? one;
+  int? two;
+  B([this.one = 2, this.two = 2]) : super._();
+}
+void f() {
+  A();
+  A(null, null);
+  A(1, 1);
+}
+''');
+  }
+
+  test_redirectingFactoryConstructor_named() async {
+    await assertNoDiagnostics(r'''
+class A {
+  factory A({int? value}) = B;
+  A._();
+}
+class B extends A {
+  B({int? value = 2}) : super._();
+}
+void f() {
+  A();
+  A(value: null);
+  A(value: 1);
+}
+''');
+  }
+
+  test_redirectingFactoryConstructor_named_redundant() async {
+    await assertDiagnostics(r'''
+class A {
+  factory A({int? value}) = B;
+  A._();
+}
+class B extends A {
+  B({int? value = 2}) : super._();
+}
+void f() {
+  A(value: 2);
+}
+''', [
+      lint(124, 8),
+    ]);
+  }
+
+  test_redirectingFactoryConstructor_namedArgumentsAnywhere() async {
+    await assertNoDiagnostics(r'''
+class A {
+  factory A(int? one, int? two, {int? three}) = B;
+  A._();
+}
+class B extends A {
+  B(int? one, int? two, {int? three = 3}) : super._();
+}
+void f() {
+  A(1, 2);
+  A(1, three: null, 2);
+  A(1, 2, three: null);
+  A(1, three: 4, 2);
+  A(three: 4, 1, 2);
+}
+''');
+  }
+
+  test_redirectingFactoryConstructor_namedArgumentsAnywhere_redundant() async {
+    await assertDiagnostics(r'''
+class A {
+  factory A(int? one, int? two, {int? three}) = B;
+  A._();
+}
+class B extends A {
+  B(int? one, int? two, {int? three = 3}) : super._();
+}
+void f() {
+  A(1, three: 3, 2);
+}
+''', [
+      lint(167, 8),
+    ]);
+  }
+
+  test_redirectingFactoryConstructor_nested() async {
+    await assertNoDiagnostics(r'''
+class A {
+  factory A([num? value]) = B;
+  A._();
+}
+class B extends A {
+  factory B([num? value]) = C;
+  B._() : super._();
+}
+class C extends B {
+  num? value;
+  C([this.value = 2]) : super._();
+
+  @override
+  String toString() => '$value';
+}
+void f() {
+  A();
+  A(null);
+  A(1);
+}
+''');
+  }
+
+  test_redirectingFactoryConstructor_redundant() async {
+    await assertDiagnostics(r'''
+class A {
+  factory A([int? value]) = B;
+  A._();
+}
+class B extends A {
+  B([int? value = 2]) : super._();
+}
+void f() {
+  A(2);
+}
+''', [
+      lint(124, 1),
+    ]);
+  }
+
+  test_redirectingGenerativeConstructor() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A([int? value]) : this._(value);
+  A._([int? value = 2]);
+}
+void f() {
+  A(2);
+}
+''');
+  }
+
+  test_redirectingGenerativeConstructor_named() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({int? value}) : this._(value: value);
+  A._({int? value = 2});
+}
+void f() {
+  A(value: 2);
+}
+''');
+  }
+
+  test_redirectingGenerativeConstructor_named_redundant() async {
+    await assertDiagnostics(r'''
+class A {
+  A({int? value}) : this._(value: value);
+  A._({int? value = 2});
+}
+void f() {
+  A(value: null);
+}
+''', [
+      lint(101, 4),
+    ]);
+  }
+
+  test_redirectingGenerativeConstructor_redundant() async {
+    await assertDiagnostics(r'''
+class A {
+  A([int? value]) : this._(value);
+  A._([int? value = 2]);
+}
+void f() {
+  A(null);
+}
+''', [
+      lint(87, 4),
+    ]);
+  }
+
+  test_requiredNullable() async {
+    await assertNoDiagnostics(r'''
+void f({required int? x}) { }
+
+void main() {
+  f(x: null);
+} 
+''');
+  }
+}
diff --git a/test/rules/avoid_renaming_method_parameters_test.dart b/test/rules/avoid_renaming_method_parameters_test.dart
new file mode 100644
index 0000000..b79f26f
--- /dev/null
+++ b/test/rules/avoid_renaming_method_parameters_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidRenamingMethodParametersTest);
+  });
+}
+
+@reflectiveTest
+class AvoidRenamingMethodParametersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_renaming_method_parameters';
+
+  test_rename() async {
+    await assertDiagnostics(r'''
+class C {
+  int f(int f) => f;
+}
+enum A with C {
+  a,b,c;
+  @override
+  int f(int x) => x;
+}
+''', [
+      lint(82, 1),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_returning_this_test.dart b/test/rules/avoid_returning_this_test.dart
new file mode 100644
index 0000000..dbeb20b
--- /dev/null
+++ b/test/rules/avoid_returning_this_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidReturningThisTest);
+  });
+}
+
+@reflectiveTest
+class AvoidReturningThisTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_returning_this';
+
+  test_method() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  A aa() => this;
+}
+''', [
+      lint(22, 2),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_setters_without_getters_test.dart b/test/rules/avoid_setters_without_getters_test.dart
new file mode 100644
index 0000000..16c5d66
--- /dev/null
+++ b/test/rules/avoid_setters_without_getters_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidSettersWithoutGettersTest);
+  });
+}
+
+@reflectiveTest
+class AvoidSettersWithoutGettersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_setters_without_getters';
+
+  test_enum() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  set x(int x) {}
+}
+''', [
+      lint(24, 1),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_shadowing_type_parameters_test.dart b/test/rules/avoid_shadowing_type_parameters_test.dart
new file mode 100644
index 0000000..fb713ba
--- /dev/null
+++ b/test/rules/avoid_shadowing_type_parameters_test.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidShadowingTypeParametersEnumTest);
+    defineReflectiveTests(AvoidShadowingTypeParametersTest);
+  });
+}
+
+@reflectiveTest
+class AvoidShadowingTypeParametersEnumTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_shadowing_type_parameters';
+
+  test_enum() async {
+    await assertDiagnostics(r'''
+enum E<T> {
+  a, b, c;
+  void fn<T>() {}
+}
+''', [
+      lint(33, 1),
+    ]);
+  }
+}
+
+@reflectiveTest
+class AvoidShadowingTypeParametersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_shadowing_type_parameters';
+
+  test_wrongNumberOfTypeArguments() async {
+    await assertDiagnostics(r'''
+typedef Predicate = bool <E>(E element);
+''', [
+      // No lint.
+      error(CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS, 20, 8),
+      error(CompileTimeErrorCode.NON_TYPE_AS_TYPE_ARGUMENT, 26, 1),
+      error(ParserErrorCode.EXPECTED_TOKEN, 28, 1),
+      error(CompileTimeErrorCode.UNDEFINED_CLASS, 29, 1),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_types_as_parameter_names_test.dart b/test/rules/avoid_types_as_parameter_names_test.dart
new file mode 100644
index 0000000..331db90
--- /dev/null
+++ b/test/rules/avoid_types_as_parameter_names_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidTypesAsParameterNamesTest);
+  });
+}
+
+@reflectiveTest
+class AvoidTypesAsParameterNamesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_types_as_parameter_names';
+
+  test_super() async {
+    await assertDiagnostics(r'''
+class A {
+  String a;
+  A(this.a);
+}
+class B extends A {
+  B(super.String);
+}
+''', [
+      lint(67, 6),
+    ]);
+  }
+}
diff --git a/test/rules/avoid_unused_constructor_parameters_test.dart b/test/rules/avoid_unused_constructor_parameters_test.dart
new file mode 100644
index 0000000..3c21da3
--- /dev/null
+++ b/test/rules/avoid_unused_constructor_parameters_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidUnusedConstructorParametersTest);
+  });
+}
+
+@reflectiveTest
+class AvoidUnusedConstructorParametersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_unused_constructor_parameters';
+
+  test_super() async {
+    await assertNoDiagnostics(r'''
+class A {
+  String a;
+  String b;
+  A(this.a, this.b);
+}
+class B extends A {
+  B(super.a, super.b);
+}
+''');
+  }
+}
diff --git a/test/rules/avoid_void_async_test.dart b/test/rules/avoid_void_async_test.dart
new file mode 100644
index 0000000..3db0207
--- /dev/null
+++ b/test/rules/avoid_void_async_test.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AvoidVoidAsyncTest);
+  });
+}
+
+@reflectiveTest
+class AvoidVoidAsyncTest extends LintRuleTest {
+  @override
+  String get lintRule => 'avoid_void_async ';
+
+  test_main() async {
+    await assertNoDiagnostics(r'''
+Future<void> f() async { }
+void main() async { 
+  await f();
+}
+''');
+  }
+}
diff --git a/test/rules/collection_methods_unrelated_type_test.dart b/test/rules/collection_methods_unrelated_type_test.dart
new file mode 100644
index 0000000..e886784
--- /dev/null
+++ b/test/rules/collection_methods_unrelated_type_test.dart
@@ -0,0 +1,258 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:linter/src/util/dart_type_utilities.dart';
+import 'package:linter/src/util/unrelated_types_visitor.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(CollectionMethodsUnrelatedTypeIterableTest);
+    defineReflectiveTests(CollectionMethodsUnrelatedTypeListTest);
+    defineReflectiveTests(CollectionMethodsUnrelatedTypeMapTest);
+    defineReflectiveTests(CollectionMethodsUnrelatedTypeQueueTest);
+    defineReflectiveTests(CollectionMethodsUnrelatedTypeSetTest);
+  });
+}
+
+/// This test has the most coverage of the various [UnrelatedTypesProcessors]
+/// subclasses. 99% of the complexity in each "interface/method" case is found
+/// in [UnrelatedTypesProcessors] and [typesAreUnrelated], so we do not
+/// duplicate all of the test cases for [Iterable.contains] in the test cases
+/// for [List.remove], etc.
+@reflectiveTest
+class CollectionMethodsUnrelatedTypeIterableTest extends LintRuleTest {
+  @override
+  String get lintRule => 'collection_methods_unrelated_type';
+
+  test_contains_related_dynamic() async {
+    await assertNoDiagnostics('''var x = <num>[].contains('1' as dynamic);''');
+  }
+
+  test_contains_related_implicitTarget() async {
+    await assertNoDiagnostics('''
+abstract class C implements List<num> {
+  void f() {
+    contains(1);
+  }
+}
+''');
+  }
+
+  test_contains_related_ListOfDynamic() async {
+    await assertNoDiagnostics('var x = [].contains(1);');
+  }
+
+  test_contains_related_mixedInto() async {
+    await assertNoDiagnostics('''
+mixin M {}
+class C with M {}
+late M m;
+var x = <C>[].contains(m);
+''');
+  }
+
+  test_contains_related_mixesIn() async {
+    await assertNoDiagnostics('''
+mixin M {}
+class C with M {}
+var x = <M>[].contains(C());
+''');
+  }
+
+  test_contains_related_null() async {
+    await assertNoDiagnostics('var x = <num>[].contains(null);');
+  }
+
+  test_contains_related_Object() async {
+    await assertNoDiagnostics('var x = <num>[].contains(Object());');
+  }
+
+  test_contains_related_subclassOfList() async {
+    await assertNoDiagnostics('''
+abstract class C implements List<num> {}
+void f(C c) {
+  c.contains(1);
+}
+''');
+  }
+
+  test_contains_related_subtype() async {
+    await assertNoDiagnostics('var x = <num>[].contains(1);');
+  }
+
+  test_contains_related_thisTarget() async {
+    await assertNoDiagnostics('''
+abstract class C implements List<num> {
+  void f() {
+    this.contains(1);
+  }
+}
+''');
+  }
+
+  test_contains_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num>[].contains('1');''',
+      [lint(25, 3)],
+    );
+  }
+
+  test_contains_unrelated_cascade() async {
+    await assertDiagnostics(
+      '''var x = <num>[]..contains('1');''',
+      [lint(26, 3)],
+    );
+  }
+
+  test_contains_unrelated_implicitTarget() async {
+    await assertDiagnostics('''
+abstract class C implements List<num> {
+  void f() {
+    contains('1');
+  }
+}
+''', [lint(66, 3)]);
+  }
+
+  test_contains_unrelated_subclassOfList() async {
+    await assertDiagnostics('''
+abstract class C implements List<num> {}
+void f(C c) {
+  c.contains('1');
+}
+''', [lint(68, 3)]);
+  }
+
+  test_contains_unrelated_thisTarget() async {
+    await assertDiagnostics('''
+abstract class C implements List<num> {
+  void f() {
+    this.contains('1');
+  }
+}
+''', [lint(71, 3)]);
+  }
+}
+
+@reflectiveTest
+class CollectionMethodsUnrelatedTypeListTest extends LintRuleTest {
+  @override
+  String get lintRule => 'collection_methods_unrelated_type';
+
+  test_remove_related_subtype() async {
+    await assertNoDiagnostics('var x = <num>[].remove(1);');
+  }
+
+  test_remove_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num>[].remove('1');''',
+      [lint(23, 3)],
+    );
+  }
+}
+
+@reflectiveTest
+class CollectionMethodsUnrelatedTypeMapTest extends LintRuleTest {
+  @override
+  String get lintRule => 'collection_methods_unrelated_type';
+
+  test_containsKey_related_subtype() async {
+    await assertNoDiagnostics('var x = <num, String>{}.containsKey(1);');
+  }
+
+  test_containsKey_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num, String>{}.containsKey('1');''',
+      [lint(36, 3)],
+    );
+  }
+
+  test_containsValue_related_subtype() async {
+    await assertNoDiagnostics('var x = <String, num>{}.containsValue(1);');
+  }
+
+  test_containsValue_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <String, num>{}.containsValue('1');''',
+      [lint(38, 3)],
+    );
+  }
+
+  test_index_related_subtype() async {
+    await assertNoDiagnostics('var x = <num, String>{}[1];');
+  }
+
+  test_index_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num, String>{}['1'];''',
+      [lint(24, 3)],
+    );
+  }
+
+  test_remove_related_subtype() async {
+    await assertNoDiagnostics('var x = <num, String>{}.remove(1);');
+  }
+
+  test_remove_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num, String>{}.remove('1');''',
+      [lint(31, 3)],
+    );
+  }
+}
+
+@reflectiveTest
+class CollectionMethodsUnrelatedTypeQueueTest extends LintRuleTest {
+  @override
+  String get lintRule => 'collection_methods_unrelated_type';
+
+  test_remove_related_subtype() async {
+    await assertNoDiagnostics('''
+import 'dart:collection';
+void f(Queue<num> queue) {
+  queue.remove(1);
+}
+''');
+  }
+
+  test_remove_unrelated() async {
+    await assertDiagnostics('''
+import 'dart:collection';
+void f(Queue<num> queue) {
+  queue.remove('1');
+}
+''', [lint(68, 3)]);
+  }
+}
+
+@reflectiveTest
+class CollectionMethodsUnrelatedTypeSetTest extends LintRuleTest {
+  @override
+  String get lintRule => 'collection_methods_unrelated_type';
+
+  test_lookup_related_subtype() async {
+    await assertNoDiagnostics('var x = <num>{}.lookup(1);');
+  }
+
+  test_lookup_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num>{}.lookup('1');''',
+      [lint(23, 3)],
+    );
+  }
+
+  test_remove_related_subtype() async {
+    await assertNoDiagnostics('var x = <num>{}.remove(1);');
+  }
+
+  test_remove_unrelated() async {
+    await assertDiagnostics(
+      '''var x = <num>{}.remove('1');''',
+      [lint(23, 3)],
+    );
+  }
+}
diff --git a/test/rules/conditional_uri_does_not_exist_test.dart b/test/rules/conditional_uri_does_not_exist_test.dart
new file mode 100644
index 0000000..3fd84f1
--- /dev/null
+++ b/test/rules/conditional_uri_does_not_exist_test.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConditionalUriDoesNotExistTest);
+  });
+}
+
+@reflectiveTest
+class ConditionalUriDoesNotExistTest extends LintRuleTest {
+  @override
+  bool get addMetaPackageDep => true;
+
+  @override
+  String get lintRule => 'conditional_uri_does_not_exist';
+
+  test_missingDartLibraries() async {
+    await assertDiagnostics(
+      r'''
+import ''
+    if (dart.library.io) 'dart:missing_1'
+    if (dart.library.html) 'dart:async'
+    if (dart.library.async) 'dart:missing_2';
+''',
+      [
+        error(HintCode.UNUSED_IMPORT, 7, 2),
+        lint(35, 16, messageContains: 'dart:missing_1'),
+        lint(120, 16, messageContains: 'dart:missing_2'),
+      ],
+    );
+  }
+
+  test_missingFiles() async {
+    newFile2('$testPackageRootPath/lib/exists.dart', '');
+
+    await assertDiagnostics(
+      r'''
+import ''
+    if (dart.library.io) 'missing_1.dart'
+    if (dart.library.html) 'exists.dart'
+    if (dart.library.async) 'missing_2.dart';
+''',
+      [
+        error(HintCode.UNUSED_IMPORT, 7, 2),
+        lint(35, 16, messageContains: 'missing_1.dart'),
+        lint(121, 16, messageContains: 'missing_2.dart'),
+      ],
+    );
+  }
+
+  test_missingPackages() async {
+    await assertDiagnostics(
+      r'''
+import ''
+    if (dart.library.io) 'package:meta/missing_1.dart'
+    if (dart.library.html) 'package:meta/meta.dart'
+    if (dart.library.io) 'package:foo/missing_2.dart';
+''',
+      [
+        error(HintCode.UNUSED_IMPORT, 7, 2),
+        lint(35, 29, messageContains: 'missing_1.dart'),
+        lint(142, 28, messageContains: 'missing_2.dart'),
+      ],
+    );
+  }
+}
diff --git a/test/rules/constant_identifier_names_test.dart b/test/rules/constant_identifier_names_test.dart
new file mode 100644
index 0000000..4d81edb
--- /dev/null
+++ b/test/rules/constant_identifier_names_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConstantIdentifierNamesRecordsTest);
+  });
+}
+
+@reflectiveTest
+class ConstantIdentifierNamesRecordsTest extends LintRuleTest {
+  @override
+  List<String> get experiments => ['records'];
+
+  @override
+  String get lintRule => 'constant_identifier_names';
+
+  test_recordTypeDeclarations() async {
+    await assertDiagnostics(r'''
+const RR = (x: 1);
+''', [
+      lint(6, 2),
+    ]);
+  }
+
+  test_recordTypeDeclarations_ok() async {
+    await assertNoDiagnostics(r'''
+const r = (x: 1);
+''');
+  }
+}
diff --git a/test/rules/dangling_library_doc_comments_test.dart b/test/rules/dangling_library_doc_comments_test.dart
new file mode 100644
index 0000000..98fd16d
--- /dev/null
+++ b/test/rules/dangling_library_doc_comments_test.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(DanglingLibraryDocCommentsTest);
+  });
+}
+
+@reflectiveTest
+class DanglingLibraryDocCommentsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'dangling_library_doc_comments';
+
+  test_docComment_aboveDeclaration() async {
+    await assertDiagnostics(
+      r'''
+/// Doc comment.
+
+class C {}
+''',
+      [lint(0, 16)],
+    );
+  }
+
+  test_docComment_aboveDeclaration_endingInReference() async {
+    await assertNoDiagnostics(r'''
+/// Doc comment [C]
+class C {}
+''');
+  }
+
+  test_docComment_aboveDeclarationWithAnnotation() async {
+    await assertNoDiagnostics(r'''
+/// Doc comment.
+@deprecated
+class C {}
+''');
+  }
+
+  test_docComment_aboveDeclarationWithDocComment() async {
+    await assertDiagnostics(
+      r'''
+/// Library comment.
+
+/// Class comment.
+class C {}
+''',
+      [lint(0, 20)],
+    );
+  }
+
+  test_docComment_aboveDeclarationWithOtherComment1() async {
+    await assertNoDiagnostics(r'''
+/// Doc comment.
+// Comment.
+class C {}
+''');
+  }
+
+  test_docComment_aboveDeclarationWithOtherComment2() async {
+    await assertDiagnostics(
+      r'''
+/// Doc comment.
+
+// Comment.
+class C {}
+''',
+      [lint(0, 16)],
+    );
+  }
+
+  test_docComment_aboveDeclarationWithOtherComment3() async {
+    await assertDiagnostics(
+      r'''
+/// Doc comment.
+// Comment.
+
+class C {}
+''',
+      [lint(0, 16)],
+    );
+  }
+
+  test_docComment_aboveDeclarationWithOtherComment4() async {
+    await assertNoDiagnostics(r'''
+/// Doc comment.
+// Comment.
+/* Comment 2. */
+class C {}
+''');
+  }
+
+  test_docComment_atEndOfFile() async {
+    await assertDiagnostics(
+      r'''
+/// Doc comment with [int].
+''',
+      [lint(0, 27)],
+    );
+  }
+
+  test_docComment_atEndOfFile_precededByComment() async {
+    await assertDiagnostics(
+      r'''
+// Copyright something.
+
+/// Doc comment with [int].
+''',
+      [lint(25, 27)],
+    );
+  }
+
+  test_docComment_attachedToDeclaration() async {
+    await assertNoDiagnostics(r'''
+/// Doc comment.
+class C {}
+''');
+  }
+
+  test_docComment_onFirstDirective() async {
+    await assertDiagnostics(
+      r'''
+/// Doc comment.
+export 'dart:math';
+''',
+      [lint(0, 16)],
+    );
+  }
+
+  test_docComment_onLaterDirective() async {
+    await assertNoDiagnostics(r'''
+export 'dart:math';
+/// Doc comment for some reason.
+export 'dart:io';
+''');
+  }
+
+  test_docComment_onLibraryDirective() async {
+    await assertNoDiagnostics(r'''
+/// Doc comment.
+library l;
+''');
+  }
+}
diff --git a/test/rules/deprecated_consistency_test.dart b/test/rules/deprecated_consistency_test.dart
new file mode 100644
index 0000000..b2de35e
--- /dev/null
+++ b/test/rules/deprecated_consistency_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(DeprecatedConsistencyTest);
+  });
+}
+
+@reflectiveTest
+class DeprecatedConsistencyTest extends LintRuleTest {
+  @override
+  String get lintRule => 'deprecated_consistency';
+
+  test_superInit() async {
+    await assertDiagnostics(r'''
+class A {
+  String? a;
+  A({@deprecated this.a});
+}
+
+class B extends A {
+  B({super.a});
+}
+''', [
+      lint(20, 1),
+    ]);
+  }
+}
diff --git a/test/rules/discarded_futures_test.dart b/test/rules/discarded_futures_test.dart
new file mode 100644
index 0000000..93d0591
--- /dev/null
+++ b/test/rules/discarded_futures_test.dart
@@ -0,0 +1,225 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(DiscardedFuturesTest);
+  });
+}
+
+@reflectiveTest
+class DiscardedFuturesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'discarded_futures';
+
+  test_assignment_ok() async {
+    await assertNoDiagnostics(r'''
+var handler = <String, Function>{};
+
+void ff(String command) {
+  handler[command] = () async {
+    await g();
+    g();
+  };
+}
+Future<int> g() async => 0;
+''');
+  }
+
+  test_constructor() async {
+    await assertDiagnostics(r'''
+class A {
+  A() {
+    g();
+  }
+}
+
+Future<int> g() async => 0;
+''', [
+      lint(22, 1),
+    ]);
+  }
+
+  test_field_assignment() async {
+    await assertDiagnostics(r'''
+class A {
+  var a = () {
+    g();
+  };
+}
+
+Future<int> g() async => 0;
+''', [
+      lint(29, 1),
+    ]);
+  }
+
+  test_function() async {
+    await assertDiagnostics(r'''
+void recreateDir(String path) {
+  deleteDir(path);
+  createDir(path);
+}
+
+Future<void> deleteDir(String path) async {}
+Future<void> createDir(String path) async {}
+''', [
+      lint(34, 9),
+      lint(53, 9),
+    ]);
+  }
+
+  test_function_closure() async {
+    await assertDiagnostics(r'''
+void f() {
+  () {
+    createDir('.');
+  }();
+}
+
+Future<void> createDir(String path) async {}
+''', [
+      lint(22, 9),
+    ]);
+  }
+
+  test_function_closure_ok() async {
+    await assertNoDiagnostics(r'''
+Future<void> f() async {
+  () {
+    createDir('.');
+  }();
+}
+
+Future<void> createDir(String path) async {}
+''');
+  }
+
+  test_function_expression() async {
+    await assertDiagnostics(r'''
+void f() {
+  var x = h(() => g());
+  print(x);
+}
+
+int h(Function f) => 0;
+
+Future<int> g() async => 0;
+''', [
+      lint(29, 1),
+    ]);
+  }
+
+  test_function_ok_async() async {
+    await assertNoDiagnostics(r'''
+Future<void> recreateDir(String path) async {
+  await deleteDir(path);
+  await createDir(path);
+}
+
+Future<void> deleteDir(String path) async {}
+Future<void> createDir(String path) async {}
+''');
+  }
+
+  test_function_ok_return_invocation() async {
+    await assertNoDiagnostics(r'''
+Future<int> f() {
+  return g();
+}
+Future<int> g() async => 0;
+''');
+  }
+
+  test_function_ok_unawaited() async {
+    await assertNoDiagnostics(r'''
+import 'dart:async';
+
+void recreateDir(String path) {
+  unawaited(deleteDir(path));
+  unawaited(createDir(path));
+}
+
+Future<void> deleteDir(String path) async {}
+Future<void> createDir(String path) async {}
+''');
+  }
+
+  test_method() async {
+    await assertDiagnostics(r'''
+class Dir{
+  void recreateDir(String path) {
+    deleteDir(path);
+    createDir(path);
+  }
+  
+  Future<void> deleteDir(String path) async {}
+  Future<void> createDir(String path) async {}
+}
+''', [
+      lint(49, 9),
+      lint(70, 9),
+    ]);
+  }
+
+  test_topLevel_assignment() async {
+    await assertDiagnostics(r'''
+var a = () {
+  g();
+};
+
+Future<int> g() async => 0;
+''', [
+      lint(15, 1),
+    ]);
+  }
+
+  test_topLevel_assignment_expression_body() async {
+    await assertDiagnostics(r'''
+var a = () => g();
+
+Future<int> g() async => 0;
+''', [
+      lint(14, 1),
+    ]);
+  }
+
+  test_topLevel_assignment_ok_async() async {
+    await assertNoDiagnostics(r'''
+var a = () async {
+  g();
+};
+
+Future<int> g() async => 0;
+''');
+  }
+
+  test_topLevel_assignment_ok_future() async {
+    await assertNoDiagnostics(r'''
+Future<int> a = g();
+
+Future<int> g() async => 0;
+''');
+  }
+
+  test_variable_assignment() async {
+    await assertDiagnostics(r'''
+var handler = <String, Function>{};
+
+void ff(String command) {
+  handler[command] = () {
+    g();
+  };
+}
+
+Future<int> g() async => 0;
+''', [
+      lint(93, 1),
+    ]);
+  }
+}
diff --git a/test/rules/enable_null_safety_test.dart b/test/rules/enable_null_safety_test.dart
new file mode 100644
index 0000000..2d009d4
--- /dev/null
+++ b/test/rules/enable_null_safety_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(EnableNullSafetyTest);
+  });
+}
+
+@reflectiveTest
+class EnableNullSafetyTest extends LintRuleTest {
+  @override
+  String get lintRule => 'enable_null_safety';
+
+  test_2_11() async {
+    await assertDiagnostics(r'''
+// @dart=2.11
+f() {
+}
+''', [
+      lint(0, 13),
+    ]);
+  }
+
+  test_2_12() async {
+    await assertNoDiagnostics(r'''
+// @dart=2.12
+f() {
+}
+''');
+  }
+
+  test_2_8() async {
+    await assertDiagnostics(r'''
+// @dart=2.8
+f() {
+}
+''', [
+      lint(0, 12),
+    ]);
+  }
+
+  test_2_8_shebang() async {
+    await assertDiagnostics(r'''
+#!/usr/bin/dart
+// @dart=2.8
+f() {
+}
+''', [
+      lint(16, 12),
+    ]);
+  }
+}
diff --git a/test/rules/file_names_test.dart b/test/rules/file_names_test.dart
new file mode 100644
index 0000000..60c9d41
--- /dev/null
+++ b/test/rules/file_names_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(FileNamesInvalidTest);
+    defineReflectiveTests(FileNamesNonStrictTest);
+  });
+}
+
+@reflectiveTest
+class FileNamesInvalidTest extends LintRuleTest {
+  @override
+  String get lintRule => 'file_names';
+
+  @override
+  String get testFilePath => '$testPackageLibPath/a-test.dart';
+
+  test_invalidName() async {
+    await assertDiagnostics(r'''
+class A { }
+''', [
+      lint(0, 0),
+    ]);
+  }
+}
+
+@reflectiveTest
+class FileNamesNonStrictTest extends LintRuleTest {
+  @override
+  String get lintRule => 'file_names';
+
+  @override
+  String get testFilePath => '$testPackageLibPath/non-strict.css.dart';
+
+  test_validName() async {
+    await assertNoDiagnostics(r'''
+class A { }
+''');
+  }
+}
diff --git a/test/rules/flutter_style_todos_test.dart b/test/rules/flutter_style_todos_test.dart
new file mode 100644
index 0000000..1fa4946
--- /dev/null
+++ b/test/rules/flutter_style_todos_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(FlutterStyleTodosTest);
+  });
+}
+
+@reflectiveTest
+class FlutterStyleTodosTest extends LintRuleTest {
+  @override
+  String get lintRule => 'flutter_style_todos';
+
+  test_badPatterns() async {
+    await assertDiagnostics(
+      r'''
+// TODO something
+// Todo something
+// todo something
+// TODO(somebody) something
+// TODO: something
+// Todo(somebody): something
+// todo(somebody): something
+// ToDo(somebody): something
+// TODO(somebody): something, github.com/flutter/flutter
+// ToDo(somebody): something, https://github.com/flutter/flutter
+''',
+      [
+        lint(0, 17),
+        lint(18, 17),
+        lint(36, 17),
+        lint(54, 27),
+        lint(82, 18),
+        lint(101, 28),
+        lint(130, 28),
+        lint(159, 28),
+        lint(245, 64),
+      ],
+    );
+  }
+
+  test_goodPatterns() async {
+    await assertNoDiagnostics(
+      r'''
+// TODO(somebody): something
+// TODO(somebody): something, https://github.com/flutter/flutter
+''',
+    );
+  }
+}
diff --git a/test/rules/hash_and_equals_test.dart b/test/rules/hash_and_equals_test.dart
new file mode 100644
index 0000000..64329ea
--- /dev/null
+++ b/test/rules/hash_and_equals_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(HashAndEqualsTest);
+  });
+}
+
+@reflectiveTest
+class HashAndEqualsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'hash_and_equals';
+
+  test_enum_missingHash() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  @override
+  bool operator ==(Object other) => false;
+}
+''', [
+      error(
+          CompileTimeErrorCode.ILLEGAL_CONCRETE_ENUM_MEMBER_DECLARATION, 46, 2),
+      // no lint
+    ]);
+  }
+}
diff --git a/test/rules/library_annotations_test.dart b/test/rules/library_annotations_test.dart
new file mode 100644
index 0000000..c7eec15
--- /dev/null
+++ b/test/rules/library_annotations_test.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(LibraryAnnotationsTest);
+  });
+}
+
+@reflectiveTest
+class LibraryAnnotationsTest extends LintRuleTest {
+  @override
+  bool get addMetaPackageDep => true;
+
+  @override
+  String get lintRule => 'library_annotations';
+
+  test_classDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+class C {}
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_classTypeAliasDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+abstract class C = Object with Future;
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_enumDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+enum E { one, two }
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_exportDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+export 'dart:math';
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_extensionDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+extension E on int {}
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_functionDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+void f() {}
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_functionDeclaration_annotationWithTargetKindLibrary() async {
+    // In this library, `invalid_annotation_target` is reported (and
+    // suppressed), so we do not also report `library_annotations`.
+    await assertNoDiagnostics(
+      r'''
+import 'package:meta/meta_meta.dart';
+
+@Target({TargetKind.library})
+class TestOn {
+  const TestOn(String name);
+}
+
+// ignore: invalid_annotation_target
+@TestOn('browser')
+class C {}
+''',
+    );
+  }
+
+  test_genericTypedefDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+typedef Fn = void Function();
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_importDirective_annotationWithTargetKindLibrary() async {
+    await assertDiagnostics(
+      r'''
+@TestOn('browser')
+import 'package:meta/meta_meta.dart';
+
+@Target({TargetKind.library})
+class TestOn {
+  const TestOn(String name);
+}
+
+class C {}
+''',
+      [lint(0, 18)],
+    );
+  }
+
+  test_mixinDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+mixin M {}
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_topLevelVariableDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+var i = 1;
+''',
+      [lint(0, 29)],
+    );
+  }
+
+  test_typedefDeclaration() async {
+    await assertDiagnostics(
+      r'''
+@pragma('dart2js:late:trust')
+typedef void Fn();
+''',
+      [lint(0, 29)],
+    );
+  }
+}
diff --git a/test/rules/library_names_test.dart b/test/rules/library_names_test.dart
new file mode 100644
index 0000000..288deb0
--- /dev/null
+++ b/test/rules/library_names_test.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(LibraryNamesTest);
+  });
+}
+
+@reflectiveTest
+class LibraryNamesTest extends LintRuleTest {
+  @override
+  List<String> get experiments => ['unnamed-libraries'];
+
+  @override
+  String get lintRule => 'library_names';
+
+  test_libraryWithoutName() async {
+    await assertNoDiagnostics('''
+library;
+''');
+  }
+
+  test_lowercase() async {
+    await assertNoDiagnostics('''
+library foo;
+''');
+  }
+
+  test_noLibrary() async {
+    await assertNoDiagnostics('''
+''');
+  }
+
+  test_titlecase() async {
+    await assertDiagnostics('''
+library Foo;
+''', [
+      lint(8, 3),
+    ]);
+  }
+
+  test_uppercaseInDots() async {
+    await assertDiagnostics('''
+library one.Two.three;
+''', [
+      lint(8, 13),
+    ]);
+  }
+}
diff --git a/test/rules/library_private_types_in_public_api_test.dart b/test/rules/library_private_types_in_public_api_test.dart
new file mode 100644
index 0000000..beb6105
--- /dev/null
+++ b/test/rules/library_private_types_in_public_api_test.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(LibraryPrivateTypesInPublicApiEnumTest);
+    defineReflectiveTests(LibraryPrivateTypesInPublicApiSuperParamTest);
+  });
+}
+
+@reflectiveTest
+class LibraryPrivateTypesInPublicApiEnumTest extends LintRuleTest {
+  @override
+  String get lintRule => 'library_private_types_in_public_api';
+
+  test_enum() async {
+    await assertDiagnostics(r'''
+class _O {}
+enum E {
+  a, b, c;
+  final _O o = _O();
+  void oo(_O o) { }
+  _O get ooo => o;
+}
+''', [
+      lint(40, 2),
+      lint(63, 2),
+      lint(75, 2),
+    ]);
+  }
+}
+
+@reflectiveTest
+class LibraryPrivateTypesInPublicApiSuperParamTest extends LintRuleTest {
+  @override
+  String get lintRule => 'library_private_types_in_public_api';
+
+  test_implicitTypeFieldFormalParam() async {
+    await assertDiagnostics(r'''
+class _O {}
+class C {
+  _O _x;
+
+  C(this._x);
+  
+  Object get x => _x;
+}
+''', [
+      lint(41, 2),
+    ]);
+  }
+
+  test_implicitTypeSuperFormalParam() async {
+    await assertDiagnostics(r'''
+class _O extends Object {}
+class _A {
+  _A(_O o);
+}
+class B extends _A {
+  B(super.o);
+}
+''', [
+      lint(83, 1),
+    ]);
+  }
+
+  test_recursiveInterfaceInheritance() async {
+    await assertDiagnostics(r'''
+class _O extends Object {}
+class A {
+  Object o;
+  A(this.o);
+}
+
+class B extends A {
+  B(_O super.o);
+}
+''', [
+      lint(89, 2),
+    ]);
+  }
+}
diff --git a/test/rules/literal_only_boolean_expressions_test.dart b/test/rules/literal_only_boolean_expressions_test.dart
new file mode 100644
index 0000000..f7b0d94
--- /dev/null
+++ b/test/rules/literal_only_boolean_expressions_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(LiteralOnlyBooleanExpressionsTest);
+  });
+}
+
+@reflectiveTest
+class LiteralOnlyBooleanExpressionsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'literal_only_boolean_expressions';
+
+  test_whileTrue() async {
+    await assertNoDiagnostics(r'''
+void f() {
+  while (true) {
+    print('!');
+  }
+}
+''');
+  }
+}
diff --git a/test/rules/missing_whitespace_between_adjacent_strings_test.dart b/test/rules/missing_whitespace_between_adjacent_strings_test.dart
new file mode 100644
index 0000000..892f679
--- /dev/null
+++ b/test/rules/missing_whitespace_between_adjacent_strings_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(MissingWhitespaceBetweenAdjacentStringsTest);
+  });
+}
+
+@reflectiveTest
+class MissingWhitespaceBetweenAdjacentStringsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'missing_whitespace_between_adjacent_strings';
+
+  test_extraPositionalArgument() async {
+    await assertDiagnostics(r'''
+void f() {
+  new Unresolved('aaa' 'bbb');
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.NEW_WITH_NON_TYPE, 17, 10),
+    ]);
+  }
+}
diff --git a/test/rules/non_constant_identifier_names_test.dart b/test/rules/non_constant_identifier_names_test.dart
new file mode 100644
index 0000000..94f6867
--- /dev/null
+++ b/test/rules/non_constant_identifier_names_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(NonConstantIdentifierNamesTest);
+    defineReflectiveTests(NonConstantIdentifierNamesRecordsTest);
+  });
+}
+
+@reflectiveTest
+class NonConstantIdentifierNamesRecordsTest extends LintRuleTest {
+  @override
+  List<String> get experiments => ['records'];
+
+  @override
+  String get lintRule => 'non_constant_identifier_names';
+
+  test_recordFields() async {
+    await assertDiagnostics(r'''
+var a = (x: 1);
+var b = (XX: 1);
+''', [
+      lint(25, 2),
+    ]);
+  }
+
+  test_recordTypeAnnotation_named() async {
+    await assertDiagnostics(r'''
+(int, {String SS, bool b})? triple;
+''', [
+      lint(14, 2),
+    ]);
+  }
+
+  test_recordTypeAnnotation_positional() async {
+    await assertDiagnostics(r'''
+(int, String SS, bool) triple = (1,'', false);
+''', [
+      lint(13, 2),
+    ]);
+  }
+
+  test_recordTypeDeclarations() async {
+    await assertDiagnostics(r'''
+var AA = (x: 1);
+const BB = (x: 1);
+''', [
+      lint(4, 2),
+    ]);
+  }
+
+  test_test_recordFields_underscores() async {
+    // This will produce a compile-time error and we don't want to over-report.
+    await assertDiagnostics(r'''
+var a = (_x: 1);
+''', [
+      // No Lint.
+      error(CompileTimeErrorCode.INVALID_FIELD_NAME_PRIVATE, 9, 2),
+    ]);
+  }
+}
+
+@reflectiveTest
+class NonConstantIdentifierNamesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'non_constant_identifier_names';
+
+  ///https://github.com/dart-lang/linter/issues/193
+  test_ignoreSyntheticNodes() async {
+    await assertDiagnostics(r'''
+class C <E>{ }
+C<int>;
+''', [
+      // No lint
+      error(ParserErrorCode.MISSING_FUNCTION_PARAMETERS, 15, 1),
+      error(CompileTimeErrorCode.DUPLICATE_DEFINITION, 15, 1),
+      error(ParserErrorCode.MISSING_FUNCTION_BODY, 21, 1),
+    ]);
+  }
+}
diff --git a/test/rules/null_closures_test.dart b/test/rules/null_closures_test.dart
new file mode 100644
index 0000000..581a9eb
--- /dev/null
+++ b/test/rules/null_closures_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(NullClosuresPreNNBDTest);
+    defineReflectiveTests(NullClosuresTest);
+  });
+}
+
+@reflectiveTest
+class NullClosuresPreNNBDTest extends LintRuleTest {
+  @override
+  String get lintRule => 'null_closures';
+
+  test_list_firstWhere() async {
+    await assertNoDiagnostics(r'''
+// @dart=2.9
+
+f() => <int>[2, 4, 6].firstWhere((e) => e.isEven, orElse: () => null);
+''');
+  }
+
+  test_list_generate() async {
+    await assertDiagnostics(r'''
+// @dart=2.9
+
+f() => List.generate(3, null);
+''', [
+      lint(38, 4),
+    ]);
+  }
+
+  test_list_where() async {
+    await assertDiagnostics(r'''
+// @dart=2.9
+
+f() => <int>[2, 4, 6].where(null);
+''', [
+      lint(42, 4),
+    ]);
+  }
+
+  test_map_putIfAbsent() async {
+    await assertDiagnostics(r'''
+// @dart=2.9
+
+f() {
+  var map = <int, int>{};
+  map.putIfAbsent(7, null);
+  return map;
+}
+''', [
+      lint(67, 4),
+    ]);
+  }
+}
+
+@reflectiveTest
+class NullClosuresTest extends LintRuleTest {
+  @override
+  String get lintRule => 'null_closures';
+
+  ///https://github.com/dart-lang/linter/issues/1414
+  test_recursiveInterfaceInheritance() async {
+    await assertDiagnostics(r'''
+class A extends B {
+  A(int x);
+}
+
+class B extends A {}
+
+void test_cycle() {
+  A(null);
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE, 6, 1),
+      error(CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE, 41, 1),
+      error(CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT, 41, 1),
+      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 81, 4),
+    ]);
+  }
+}
diff --git a/test/rules/omit_local_variable_types_test.dart b/test/rules/omit_local_variable_types_test.dart
new file mode 100644
index 0000000..22a8f9c
--- /dev/null
+++ b/test/rules/omit_local_variable_types_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(OmitLocalVariableTypesTest);
+  });
+}
+
+@reflectiveTest
+class OmitLocalVariableTypesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'omit_local_variable_types';
+
+  /// https://github.com/dart-lang/linter/issues/3016
+  @failingTest
+  test_paramIsType() async {
+    await assertDiagnostics(r'''
+T bar<T>(T d) => d;
+
+String f() {
+  String h = bar('');
+  return h;
+}
+''', [
+      lint(42, 26),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3016
+  test_typeNeededForInference() async {
+    await assertNoDiagnostics(r'''
+T bar<T>(dynamic d) => d;
+
+String f() {
+  String h = bar('');
+  return h;
+}
+''');
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3016
+  test_typeParamProvided() async {
+    await assertDiagnostics(r'''
+T bar<T>(dynamic d) => d;
+
+String f() {
+  String h = bar<String>('');
+  return h;
+}
+''', [
+      lint(42, 26),
+    ]);
+  }
+}
diff --git a/test/rules/overridden_fields_test.dart b/test/rules/overridden_fields_test.dart
new file mode 100644
index 0000000..a54c19d
--- /dev/null
+++ b/test/rules/overridden_fields_test.dart
@@ -0,0 +1,134 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(OverriddenFieldsTest);
+  });
+}
+
+@reflectiveTest
+class OverriddenFieldsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'overridden_fields';
+
+  /// https://github.com/dart-lang/linter/issues/2874
+  test_conflictingStaticAndInstance() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static final String field = 'value';
+}
+
+class B extends A {
+  String field = 'otherValue';
+}
+''');
+  }
+
+  test_recursiveInterfaceInheritance() async {
+    // Produces a recursive_interface_inheritance diagnostic.
+    await assertDiagnostics(r'''
+class A extends B {}
+class B extends A {
+  int field = 0;
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE, 6, 1),
+      error(CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE, 27, 1),
+    ]);
+  }
+
+  test_mixinInheritsFromNotObject() async {
+    // See: https://github.com/dart-lang/linter/issues/2969
+    // Preserves existing testing logic but has so many misuses of mixins that
+    // that it's hard to know how much tested logic is intentional.
+    await assertDiagnostics(r'''
+class Base {
+  Object field = 'lorem';
+
+  Object something = 'change';
+}
+
+class Bad1 extends Base {
+  @override
+  final x = 1, field = 'ipsum'; // LINT
+}
+
+class GC11 extends Bad1 {
+  @override
+  Object something = 'done';
+
+  Object gc33 = 'gc33';
+}
+
+class GC13 extends Object with Bad1 {
+  @override
+  Object something = 'done';
+
+  @override
+  Object field = 'lint';
+}
+
+abstract class GC21 extends GC11 {
+  @override
+  Object something = 'done';
+}
+
+class GC23 extends Object with GC13 {
+  @override
+  Object something = 'done';
+
+  @override
+  Object field = 'lint';
+}
+
+abstract class GC31 extends GC13 {
+  @override
+  Object something = 'done';
+}
+
+abstract class GC32 implements GC13 {
+  @override
+  Object something = 'done';
+}
+
+class GC33 extends GC21 with GC13 {
+  @override
+  Object something = 'done';
+
+  @override
+  Object gc33 = 'yada';
+}
+
+class GC34 extends GC33 {
+  @override
+  var x = 3;
+
+  @override
+  Object gc33 = 'yada';
+}
+''', [
+      error(HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD, 120, 1),
+      lint(127, 5),
+      lint(202, 9),
+      error(CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT, 281, 4),
+      lint(351, 5),
+      lint(426, 9),
+      error(CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT, 480, 4),
+      lint(508, 9),
+      lint(550, 5),
+      lint(625, 9),
+      error(CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT, 759, 4),
+      lint(787, 9),
+      lint(829, 4),
+      lint(891, 1),
+      lint(920, 4),
+    ]);
+  }
+}
diff --git a/test/rules/parameter_assignments_test.dart b/test/rules/parameter_assignments_test.dart
new file mode 100644
index 0000000..52758eb
--- /dev/null
+++ b/test/rules/parameter_assignments_test.dart
@@ -0,0 +1,179 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ParameterAssignmentsTest);
+  });
+}
+
+@reflectiveTest
+class ParameterAssignmentsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'parameter_assignments';
+
+  @FailingTest(reason: 'Closures not implemented')
+  test_closure_assignment() async {
+    await assertDiagnostics(r'''
+void f() {
+  (int p) {
+    p = 2;
+  }(2);
+}
+''', [
+      lint(27, 5),
+    ]);
+  }
+
+  test_function_assignment() async {
+    await assertDiagnostics(r'''
+void f(int p) {
+  p = 4;
+}
+''', [
+      lint(18, 5),
+    ]);
+  }
+
+  test_function_incrementAssignment() async {
+    await assertDiagnostics(r'''
+void f(int p) {
+  p += 4;
+}
+''', [
+      lint(18, 6),
+    ]);
+  }
+
+  test_function_named_default() async {
+    await assertDiagnostics(r'''
+void f({int p: 5}) {
+  print(p++);
+}
+''', [
+      lint(29, 3),
+    ]);
+  }
+
+  test_function_named_optional_ok() async {
+    await assertNoDiagnostics(r'''
+void f({int? optional}) {
+  optional ??= 8;
+}
+''');
+  }
+
+  test_function_ok_noAssignment() async {
+    await assertNoDiagnostics(r'''
+void f(String p) {
+  print(p);
+}
+''');
+  }
+
+  test_function_ok_shadow() async {
+    await assertDiagnostics(r'''
+void f(String? p) {
+  if (p == null) {
+    int p = 2;
+    p = 3;
+  }
+}
+''', [
+      // No lint.
+      error(HintCode.UNUSED_LOCAL_VARIABLE, 47, 1),
+    ]);
+  }
+
+  test_function_positional_optional_assignedTwice() async {
+    await assertDiagnostics(r'''
+void f([int? optional]) {
+  optional ??= 8;
+  optional ??= 16;
+}
+''', [
+      error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 59, 2),
+      lint(46, 15),
+    ]);
+  }
+
+  test_function_positional_optional_ok() async {
+    await assertNoDiagnostics(r'''
+void f([int? optional]) {
+  optional ??= 8;
+}
+''');
+  }
+
+  test_function_positional_optional_re_incremented() async {
+    await assertDiagnostics(r'''
+void f([int? optional]) {
+  optional ??= 8;
+  optional += 16;
+}
+''', [
+      lint(46, 14),
+    ]);
+  }
+
+  test_function_positional_optional_reassigned() async {
+    await assertDiagnostics(r'''
+void f([int? optional]) {
+  optional ??= 8;
+  optional = 16;
+}
+''', [
+      lint(46, 13),
+    ]);
+  }
+
+  test_function_postfix() async {
+    await assertDiagnostics(r'''
+void f(int p) {
+  p++;
+}
+''', [
+      lint(18, 3),
+    ]);
+  }
+
+  test_function_prefix() async {
+    await assertDiagnostics(r'''
+void f(int p) {
+  ++p;
+}
+''', [
+      lint(18, 3),
+    ]);
+  }
+
+  test_localFunction() async {
+    await assertDiagnostics(r'''
+void f(int p) {
+  void g() {
+    p = 3;
+  }
+  g();
+}
+''', [
+      lint(33, 5),
+    ]);
+  }
+
+  test_member_setter() async {
+    await assertDiagnostics(r'''
+class A {
+  set x(int v) {
+    v = 5;
+  }
+}
+''', [
+      lint(31, 5),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_asserts_in_initializer_lists_test.dart b/test/rules/prefer_asserts_in_initializer_lists_test.dart
new file mode 100644
index 0000000..0bb0e51
--- /dev/null
+++ b/test/rules/prefer_asserts_in_initializer_lists_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferAssertsInInitializerListsTest);
+    defineReflectiveTests(PreferAssertsInInitializerListsSuperTest);
+  });
+}
+
+@reflectiveTest
+class PreferAssertsInInitializerListsSuperTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_asserts_in_initializer_lists';
+
+  test_super() async {
+    await assertDiagnostics(r'''
+class A {
+  final int a;
+  A(this.a);
+}
+
+class B extends A {
+  B(super.a) {
+    assert(a != 0);
+  }
+}
+''', [
+      lint(80, 6),
+    ]);
+  }
+}
+
+@reflectiveTest
+class PreferAssertsInInitializerListsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_asserts_in_initializer_lists';
+
+  test_nonBoolExpression() async {
+    await assertDiagnostics(r'''
+class A {
+  bool? f;
+  A() {
+    assert(()
+    {
+      f = true;
+      return false;
+    });
+  }
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.NON_BOOL_EXPRESSION, 40, 50),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_collection_literals_test.dart b/test/rules/prefer_collection_literals_test.dart
new file mode 100644
index 0000000..4d32c93
--- /dev/null
+++ b/test/rules/prefer_collection_literals_test.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferCollectionLiteralsPreNNBDTest);
+    defineReflectiveTests(PreferCollectionLiteralsTest);
+  });
+}
+
+@reflectiveTest
+class PreferCollectionLiteralsPreNNBDTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_collection_literals';
+
+  test_list_empty() async {
+    await assertDiagnostics(r'''
+// @dart=2.9    
+var list = List(); 
+''', [
+      lint(28, 6),
+    ]);
+  }
+
+  test_list_inList() async {
+    await assertDiagnostics(r'''
+// @dart=2.9    
+var list = [[], List()];
+''', [
+      lint(33, 6),
+    ]);
+  }
+
+  test_list_sized() async {
+    await assertNoDiagnostics(r'''
+// @dart=2.9    
+var list = List(5);
+''');
+  }
+}
+
+@reflectiveTest
+class PreferCollectionLiteralsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_collection_literals';
+
+  /// https://github.com/dart-lang/linter/issues/2985
+  test_linkedHashSetParameter_named_type_required() async {
+    await assertNoDiagnostics(r'''
+import 'dart:collection';
+    
+class Foo {}
+
+void a({required LinkedHashSet<Foo> some}) {}
+
+void c() {
+  a(some: LinkedHashSet<Foo>());
+}
+''');
+  }
+
+  test_linkedHashSetParameter_named_type_unrequired() async {
+    await assertDiagnostics(r'''
+import 'dart:collection';
+    
+class Foo {}
+
+void a({required Set<Foo> some}) {}
+
+void c() {
+  a(some: LinkedHashSet<Foo>());
+}
+''', [
+      lint(103, 20),
+    ]);
+  }
+
+  test_linkedHashSetParameter_type_required() async {
+    await assertNoDiagnostics(r'''
+import 'dart:collection';
+    
+class Foo {}
+
+void b(LinkedHashSet<Foo> some) {}
+
+void c() {
+  b(LinkedHashSet());
+}
+''');
+  }
+
+  test_linkedHashSetParameter_type_unrequired() async {
+    await assertDiagnostics(r'''
+import 'dart:collection';
+    
+class Foo {}
+
+void b(Set<Foo> some) {}
+
+void c() {
+  b(LinkedHashSet<Foo>());
+}
+''', [
+      lint(86, 20),
+    ]);
+  }
+
+  test_undefinedFunction() async {
+    await assertDiagnostics(r'''
+import 'dart:collection';
+    
+void f() {
+  printUnresolved(LinkedHashSet<int>());
+}
+''', [
+      // No lints.
+      error(CompileTimeErrorCode.UNDEFINED_FUNCTION, 44, 15),
+    ]);
+  }
+
+  @failingTest
+  test_closure_returns_linkedHashSet() async {
+    await assertDiagnostics(r'''
+import 'dart:collection';
+
+void a(Set<int> Function() f) {}
+
+void c() {
+  a(() => LinkedHashSet<int>());
+}
+''', [
+      lint(82, 20),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_const_constructors_in_immutables_test.dart b/test/rules/prefer_const_constructors_in_immutables_test.dart
new file mode 100644
index 0000000..5a2a725
--- /dev/null
+++ b/test/rules/prefer_const_constructors_in_immutables_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferConstConstructorsInImmutablesTest);
+  });
+}
+
+@reflectiveTest
+class PreferConstConstructorsInImmutablesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_const_constructors_in_immutables';
+
+  test_returnOfInvalidType() async {
+    await assertDiagnostics(r'''
+class F {
+  factory F.fc() => null;
+}
+''', [
+      // No lint
+      error(
+          CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_CONSTRUCTOR, 30, 4),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_const_constructors_test.dart b/test/rules/prefer_const_constructors_test.dart
new file mode 100644
index 0000000..18bdb61
--- /dev/null
+++ b/test/rules/prefer_const_constructors_test.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferConstConstructorsTest);
+  });
+}
+
+@reflectiveTest
+class PreferConstConstructorsTest extends LintRuleTest {
+  @override
+  bool get addMetaPackageDep => true;
+
+  @override
+  String get lintRule => 'prefer_const_constructors';
+
+  @FailingTest(issue: 'https://github.com/dart-lang/linter/issues/3389')
+  test_deferred_arg() async {
+    newFile2('$testPackageLibPath/a.dart', '''
+class A {
+  const A();
+}
+
+const aa = A();
+''');
+
+    await assertNoDiagnostics(r'''
+import 'a.dart' deferred as a;
+
+class B {
+  const B(Object a);
+}
+
+main() {
+  var b = B(a.aa);
+  print(b);
+}   
+''');
+  }
+
+  test_extraPositionalArgument() async {
+    await assertDiagnostics(r'''
+import 'package:meta/meta.dart';
+
+class K {
+  @literal
+  const K();
+}
+
+K k() {
+  var kk = K();
+  return kk;
+}
+''', [
+      // No lint
+      error(HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR, 90, 3),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_const_declarations_test.dart b/test/rules/prefer_const_declarations_test.dart
new file mode 100644
index 0000000..217d0f9
--- /dev/null
+++ b/test/rules/prefer_const_declarations_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferConstDeclarationsTest);
+  });
+}
+
+@reflectiveTest
+class PreferConstDeclarationsTest extends LintRuleTest {
+  @override
+  List<String> get experiments => ['records'];
+
+  @override
+  String get lintRule => 'prefer_const_declarations';
+
+  test_recordLiteral() async {
+    await assertDiagnostics(r'''
+final tuple = const ("first", 2, true);
+''', [
+      lint(0, 38),
+    ]);
+  }
+
+  test_test_recordLiteral_nonConst() async {
+    await assertNoDiagnostics(r'''
+final tuple = (1, () {});
+''');
+  }
+
+  test_test_recordLiteral_ok() async {
+    await assertNoDiagnostics(r'''
+const record = (number: 123, name: "Main", type: "Street");
+''');
+  }
+}
diff --git a/test/rules/prefer_const_literals_to_create_immutables_test.dart b/test/rules/prefer_const_literals_to_create_immutables_test.dart
new file mode 100644
index 0000000..8ea207e
--- /dev/null
+++ b/test/rules/prefer_const_literals_to_create_immutables_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferConstLiteralsToCreateImmutablesTest);
+  });
+}
+
+@reflectiveTest
+class PreferConstLiteralsToCreateImmutablesTest extends LintRuleTest {
+  @override
+  bool get addMetaPackageDep => true;
+
+  @override
+  String get lintRule => 'prefer_const_literals_to_create_immutables.dart';
+
+  test_missingRequiredArgument() async {
+    await assertDiagnostics(r'''
+import 'package:meta/meta.dart';
+
+@immutable
+class K {
+  final List<K> children;
+  const K({required this.children});
+}
+
+final k = K(
+  children: <K>[for (var i = 0; i < 5; ++i) K()], // OK
+);
+''', [
+      // No lint
+      error(CompileTimeErrorCode.MISSING_REQUIRED_ARGUMENT, 178, 1),
+    ]);
+  }
+
+  test_newWithNonType() async {
+    await assertDiagnostics(r'''
+var e1 = new B([]); // OK
+''', [
+      // No lint
+      error(CompileTimeErrorCode.NEW_WITH_NON_TYPE, 13, 1),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_contains_test.dart b/test/rules/prefer_contains_test.dart
new file mode 100644
index 0000000..d4d4519
--- /dev/null
+++ b/test/rules/prefer_contains_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferContainsTest);
+  });
+}
+
+@reflectiveTest
+class PreferContainsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_contains';
+
+  test_argumentTypeNotAssignable() async {
+    await assertDiagnostics(r'''
+List<int> list = [];
+condition() {
+  var next;
+  while ((next = list.indexOf('{')) != -1) {}
+}
+''', [
+      // No lint
+      error(HintCode.UNUSED_LOCAL_VARIABLE, 41, 4),
+      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 77, 3),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3546
+  test_secondArgNonZero() async {
+    await assertNoDiagnostics(r'''
+bool b = '11'.indexOf('2', 1) == -1;
+''');
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3546
+  test_secondArgZero() async {
+    await assertDiagnostics(r'''
+bool b = '11'.indexOf('2', 0) == -1;
+''', [
+      lint(9, 26),
+    ]);
+  }
+
+  test_unnecessaryCast() async {
+    await assertDiagnostics(r'''
+bool le3 = ([].indexOf(1) as int) > -1;
+''', [
+      lint(11, 27),
+      error(HintCode.UNNECESSARY_CAST, 12, 20),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_equal_for_default_values_test.dart b/test/rules/prefer_equal_for_default_values_test.dart
new file mode 100644
index 0000000..364b6cd
--- /dev/null
+++ b/test/rules/prefer_equal_for_default_values_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferEqualForDefaultValuesTest);
+  });
+}
+
+@reflectiveTest
+class PreferEqualForDefaultValuesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_equal_for_default_values';
+
+  test_super() async {
+    // As of 2.19, this is a warning and the lint is a no-op.
+    await assertNoDiagnostics(r'''
+class A {
+  String? a;
+  A({this.a});
+}
+
+class B extends A {
+  B({super.a : ''});
+}
+''');
+  }
+}
diff --git a/test/rules/prefer_final_fields_test.dart b/test/rules/prefer_final_fields_test.dart
new file mode 100644
index 0000000..97a9077
--- /dev/null
+++ b/test/rules/prefer_final_fields_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferFinalFieldsTest);
+  });
+}
+
+@reflectiveTest
+class PreferFinalFieldsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_final_fields';
+
+  test_enum() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  int _x = 0;
+  int get x => _x;
+}
+''', [
+      // No Lint.
+      error(CompileTimeErrorCode.NON_FINAL_FIELD_IN_ENUM, 24, 2),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_final_parameters_test.dart b/test/rules/prefer_final_parameters_test.dart
new file mode 100644
index 0000000..b21feb7
--- /dev/null
+++ b/test/rules/prefer_final_parameters_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferFinalParametersTest);
+  });
+}
+
+@reflectiveTest
+class PreferFinalParametersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_final_parameters';
+
+  test_superParameter() async {
+    await assertDiagnostics('''
+class D {
+  D(final int superParameter);
+}
+
+class E extends D {
+  E(super.superParameter); // OK
+}
+''', []);
+  }
+
+  test_superParameter_optional() async {
+    await assertDiagnostics('''
+class A {
+  final String? a;
+
+  A({this.a});
+}
+
+class B extends A {
+  B({super.a}); // OK
+}
+''', []);
+  }
+}
diff --git a/test/rules/prefer_generic_function_type_aliases_test.dart b/test/rules/prefer_generic_function_type_aliases_test.dart
new file mode 100644
index 0000000..89cc50b
--- /dev/null
+++ b/test/rules/prefer_generic_function_type_aliases_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferGenericFunctionTypeAliasesTest);
+  });
+}
+
+@reflectiveTest
+class PreferGenericFunctionTypeAliasesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_generic_function_type_aliases';
+
+  /// https://github.com/dart-lang/linter/issues/2777
+  test_undefinedFunction() async {
+    await assertDiagnostics(r'''
+typedef Cb2
+''', [
+      // No lint
+      error(ParserErrorCode.EXPECTED_TOKEN, 8, 3),
+      error(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS, 12, 0),
+    ]);
+  }
+}
diff --git a/test/rules/prefer_spread_collections_test.dart b/test/rules/prefer_spread_collections_test.dart
new file mode 100644
index 0000000..939a34e
--- /dev/null
+++ b/test/rules/prefer_spread_collections_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PreferSpreadCollectionsTest);
+  });
+}
+
+@reflectiveTest
+class PreferSpreadCollectionsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'prefer_spread_collections';
+
+  test_constInitializedWithNonConstantValue() async {
+    await assertDiagnostics(r'''
+const thangs = [];
+const cc = []..addAll(thangs);
+''', [
+      // No lint
+      error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 30,
+          18),
+    ]);
+  }
+}
diff --git a/test/rules/public_member_api_docs_test.dart b/test/rules/public_member_api_docs_test.dart
new file mode 100644
index 0000000..83b0b38
--- /dev/null
+++ b/test/rules/public_member_api_docs_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(PublicMemberApiDocsTest);
+  });
+}
+
+@reflectiveTest
+class PublicMemberApiDocsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'public_member_api_docs';
+
+  test_enum() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  int x() => 0;
+  int get y => 1;
+}
+''', [
+      lint(5, 1),
+      lint(11, 1),
+      lint(13, 1),
+      lint(15, 1),
+      lint(24, 1),
+      lint(44, 1),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3525
+  test_extension() async {
+    await assertDiagnostics(r'''
+extension E on Object {
+  void f() { }
+}
+''', [
+      lint(10, 1),
+      lint(31, 1),
+    ]);
+  }
+
+  test_mixin_method() async {
+    await assertDiagnostics(r'''
+/// A mixin M.
+mixin M {
+  String m() => '';
+}''', [
+      lint(34, 1),
+    ]);
+  }
+
+  test_mixin_overridingMethod_OK() async {
+    await assertNoDiagnostics(r'''
+/// A mixin M.
+mixin M {
+  @override
+  String toString() => '';
+}''');
+  }
+}
diff --git a/test/rules/recursive_getters_test.dart b/test/rules/recursive_getters_test.dart
new file mode 100644
index 0000000..eeb6304
--- /dev/null
+++ b/test/rules/recursive_getters_test.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(RecursiveGettersTest);
+  });
+}
+
+@reflectiveTest
+class RecursiveGettersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'recursive_getters';
+
+  /// https://github.com/dart-lang/linter/issues/3706
+  test_constList_expressionFunctionBody() async {
+    var listContents = StringBuffer();
+    for (var i = 0; i < 100000; ++i) {
+      listContents.write('"foo", ');
+    }
+    await assertNoDiagnostics('''
+List<String> get strings => const <String>[$listContents];
+''');
+  }
+
+  test_constMap_expressionFunctionBody() async {
+    var mapContents = StringBuffer();
+    for (var i = 0; i < 100000; ++i) {
+      mapContents.write('$i : "foo", ');
+    }
+    await assertNoDiagnostics('''
+Map<int, String> get strings => const <int, String>{$mapContents};
+''');
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3706
+  test_constSet_expressionFunctionBody() async {
+    var setContents = StringBuffer();
+    for (var i = 0; i < 100000; ++i) {
+      setContents.write('"foo$i", ');
+    }
+    await assertNoDiagnostics('''
+Set<String> get strings => const <String>{$setContents};
+''');
+  }
+
+  /// https://github.com/dart-lang/linter/issues/586
+  test_nestedReference() async {
+    await assertNoDiagnostics(r'''
+class Nested {
+  final Nested _parent;
+  Nested(this._parent);
+  Nested get ancestor => _parent.ancestor;
+}
+''');
+  }
+
+  test_nestedReference_property() async {
+    await assertNoDiagnostics(r'''
+class Nested {
+  final Nested target;
+  Nested(this.target);
+  bool get isFoo {
+    var self = this;
+    return self.target.isFoo;
+  }
+}
+''');
+  }
+
+  test_referenceInListLiteral() async {
+    await assertDiagnostics(r'''
+class C {
+  List<int> get f => [1, 2, ...f];
+}
+''', [
+      lint(41, 1),
+    ]);
+  }
+
+  test_referenceInMapLiteral() async {
+    await assertDiagnostics(r'''
+class C {
+  Map<int, int> get f => {}..addAll(f);
+}
+''', [
+      lint(46, 1),
+    ]);
+  }
+
+  test_referenceInMethodCall() async {
+    await assertDiagnostics(r'''
+class C {
+  int get f {
+    print(f);
+    return 0;
+  }
+}
+''', [
+      lint(34, 1),
+    ]);
+  }
+
+  test_simpleGetter() async {
+    await assertDiagnostics(r'''
+class C {
+  int get f => f;
+}
+''', [
+      lint(25, 1),
+    ]);
+  }
+
+  test_simpleGetter_thisPrefix() async {
+    await assertDiagnostics(r'''
+class C {
+  int get f => this.f;
+}
+''', [
+      lint(30, 1),
+    ]);
+  }
+
+  test_staticMemberReference() async {
+    await assertNoDiagnostics(r'''
+class C {
+  static final c = C();
+  int get f => c.f;
+}
+''');
+  }
+}
diff --git a/test/rules/sort_constructors_first_test.dart b/test/rules/sort_constructors_first_test.dart
new file mode 100644
index 0000000..9307160
--- /dev/null
+++ b/test/rules/sort_constructors_first_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SortConstructorsFirstTest);
+  });
+}
+
+@reflectiveTest
+class SortConstructorsFirstTest extends LintRuleTest {
+  @override
+  String get lintRule => 'sort_constructors_first';
+
+  test_ok() async {
+    await assertNoDiagnostics(r'''
+enum A {
+  a,b,c;
+  const A();
+  int f() => 0;
+}
+''');
+  }
+
+  test_unsorted() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c;
+  int f() => 0;
+  const A();
+}
+''', [
+      lint(42, 1),
+    ]);
+  }
+}
diff --git a/test/rules/sort_unnamed_constructors_first_test.dart b/test/rules/sort_unnamed_constructors_first_test.dart
new file mode 100644
index 0000000..52bab03
--- /dev/null
+++ b/test/rules/sort_unnamed_constructors_first_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SortUnnamedConstructorsFirstTest);
+  });
+}
+
+@reflectiveTest
+class SortUnnamedConstructorsFirstTest extends LintRuleTest {
+  @override
+  String get lintRule => 'sort_unnamed_constructors_first';
+
+  test_ok() async {
+    await assertNoDiagnostics(r'''
+enum A {
+  a,b,c.aa();
+  const A();
+  const A.aa();
+}
+''');
+  }
+
+  test_unsorted() async {
+    await assertDiagnostics(r'''
+enum A {
+  a,b,c.aa();
+  const A.aa();
+  const A();
+}
+''', [
+      lint(47, 1),
+    ]);
+  }
+}
diff --git a/test/rules/super_goes_last_test.dart b/test/rules/super_goes_last_test.dart
new file mode 100644
index 0000000..dbef04c
--- /dev/null
+++ b/test/rules/super_goes_last_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SuperGoesLastTest);
+  });
+}
+
+@reflectiveTest
+class SuperGoesLastTest extends LintRuleTest {
+  @override
+  String get lintRule => 'super_goes_last';
+
+  test_invalidSuperInvocation() async {
+    await assertDiagnostics(r'''
+class A {
+  int a;
+  A(this.a);
+}
+
+class C extends A {
+  int _c;
+  C(int a)
+      : super(a), _c = a + 1;
+}
+
+''', [
+      error(HintCode.UNUSED_FIELD, 61, 2),
+      error(CompileTimeErrorCode.SUPER_INVOCATION_NOT_LAST, 84, 5),
+      lint(84, 8),
+    ]);
+  }
+}
diff --git a/test/rules/tighten_type_of_initializing_formals_test.dart b/test/rules/tighten_type_of_initializing_formals_test.dart
new file mode 100644
index 0000000..38af169
--- /dev/null
+++ b/test/rules/tighten_type_of_initializing_formals_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(TightenTypeOfInitializingFormalsTest);
+  });
+}
+
+@reflectiveTest
+class TightenTypeOfInitializingFormalsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'tighten_type_of_initializing_formals';
+
+  test_superInit() async {
+    await assertDiagnostics(r'''
+class A {
+  String? a;
+  A(this.a);
+}
+
+class B extends A {
+  B(String super.a);
+}
+
+class C extends A {
+  C(super.a) : assert(a != null);
+}
+''', [
+      lint(107, 7),
+    ]);
+  }
+}
diff --git a/test/rules/type_init_formals_test.dart b/test/rules/type_init_formals_test.dart
new file mode 100644
index 0000000..276ff0e
--- /dev/null
+++ b/test/rules/type_init_formals_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(TypeInitFormalsTest);
+    defineReflectiveTests(TypeInitFormalsSuperTest);
+  });
+}
+
+@reflectiveTest
+class TypeInitFormalsSuperTest extends LintRuleTest {
+  @override
+  String get lintRule => 'type_init_formals';
+
+  test_super() async {
+    await assertDiagnostics(r'''
+class A {
+  String? a;
+  A({this.a});
+}
+
+class B extends A {
+  B({String? super.a});
+}
+''', [
+      lint(66, 7),
+    ]);
+  }
+}
+
+@reflectiveTest
+class TypeInitFormalsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'type_init_formals';
+
+  test_extraPositionalArgument() async {
+    await assertDiagnostics(r'''
+class A {
+  String? p1;
+  String p2 = '';
+  A.y({required String? this.p2});
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.FIELD_INITIALIZING_FORMAL_NOT_ASSIGNABLE, 49,
+          24),
+    ]);
+  }
+
+  test_initializingFormalForNonExistentField() async {
+    await assertDiagnostics(r'''
+class Invalid {
+  Invalid(int this.x); // OK
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD, 26,
+          10),
+    ]);
+  }
+}
diff --git a/test/rules/unawaited_futures_test.dart b/test/rules/unawaited_futures_test.dart
new file mode 100644
index 0000000..8a3a84b
--- /dev/null
+++ b/test/rules/unawaited_futures_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UnawaitedFuturesTest);
+  });
+}
+
+@reflectiveTest
+class UnawaitedFuturesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'unawaited_futures';
+
+  test_undefinedIdentifier() async {
+    await assertDiagnostics(r'''
+f() async {
+  Duration d = Duration();
+  Future.delayed(d, bar);
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 59, 3),
+    ]);
+  }
+}
diff --git a/test/rules/unnecessary_brace_in_string_interps_test.dart b/test/rules/unnecessary_brace_in_string_interps_test.dart
new file mode 100644
index 0000000..055bfbf
--- /dev/null
+++ b/test/rules/unnecessary_brace_in_string_interps_test.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UnnecessaryBraceInStringInterpsTest);
+  });
+}
+
+@reflectiveTest
+class UnnecessaryBraceInStringInterpsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'unnecessary_brace_in_string_interps';
+
+  test_simpleIdentifier() async {
+    await assertDiagnostics(r'''
+void hi(String name) {
+  print('hi: ${name}');
+}
+''', [
+      lint(36, 7),
+    ]);
+  }
+
+  test_simpleIdentifier_suffixed() async {
+    await assertNoDiagnostics(r'''
+void hi(String name) {
+  print('hi: ${name}s');
+}
+''');
+  }
+
+  test_this_methodInvocation() async {
+    await assertNoDiagnostics(r'''
+class A {
+  void hi() {
+    print('hi: ${this.toString()}');
+  }
+}
+''');
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3691
+  test_thisExpression() async {
+    await assertDiagnostics(r'''
+class A {
+  void hi() {
+    print('hi: ${this}');
+  }
+}
+''', [
+      lint(39, 7),
+    ]);
+  }
+
+  test_thisExpression_suffixed() async {
+    await assertNoDiagnostics(r'''
+class A {
+  void hi() {
+    print('${this}s');
+  }
+}
+''');
+  }
+}
diff --git a/test/rules/unnecessary_const_test.dart b/test/rules/unnecessary_const_test.dart
new file mode 100644
index 0000000..66eebc3
--- /dev/null
+++ b/test/rules/unnecessary_const_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UnnecessaryConstTest);
+  });
+}
+
+@reflectiveTest
+class UnnecessaryConstTest extends LintRuleTest {
+  @override
+  List<String> get experiments => ['records'];
+
+  @override
+  String get lintRule => 'unnecessary_const';
+
+  test_recordLiteral() async {
+    await assertDiagnostics(r'''
+const r = const (a: 1);
+''', [
+      lint(10, 12),
+    ]);
+  }
+
+  test_recordLiteral_ok() async {
+    await assertNoDiagnostics(r'''
+const r = (a: 1);
+''');
+  }
+}
diff --git a/test/rules/unnecessary_null_checks_test.dart b/test/rules/unnecessary_null_checks_test.dart
new file mode 100644
index 0000000..683b0fb
--- /dev/null
+++ b/test/rules/unnecessary_null_checks_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UnnecessaryNullChecksTest);
+  });
+}
+
+@reflectiveTest
+class UnnecessaryNullChecksTest extends LintRuleTest {
+  @override
+  String get lintRule => 'unnecessary_null_checks';
+
+  test_undefinedFunction() async {
+    await assertDiagnostics(r'''
+f6(int? p) {
+  return B() + p!; // OK
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.UNDEFINED_FUNCTION, 22, 1),
+    ]);
+  }
+}
diff --git a/test/rules/unnecessary_overrides_test.dart b/test/rules/unnecessary_overrides_test.dart
new file mode 100644
index 0000000..110cd3d
--- /dev/null
+++ b/test/rules/unnecessary_overrides_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UnnecessaryOverridesTest);
+  });
+}
+
+@reflectiveTest
+class UnnecessaryOverridesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'unnecessary_overrides';
+
+  test_enum_field() async {
+    await assertDiagnostics(r'''    
+enum A {
+  a,b,c;
+  @override
+  Type get runtimeType => super.runtimeType;
+}
+''', [
+      lint(41, 11),
+    ]);
+  }
+
+  test_enum_method() async {
+    await assertDiagnostics(r'''    
+enum A {
+  a,b,c;
+  @override
+  String toString() => super.toString();
+}
+''', [
+      lint(39, 8),
+    ]);
+  }
+
+  test_method_ok_commentsInBody() async {
+    await assertNoDiagnostics(r'''
+class A {
+  void a() { }
+}
+
+class B extends A {
+  @override
+  void a() {
+    // There's something we want to document here.
+    super.a();
+  }
+}
+''');
+  }
+
+  test_method_ok_expressionStatement_commentsInBody() async {
+    await assertNoDiagnostics(r'''
+class A {
+  void a() { }
+}
+
+class B extends A {
+  @override
+  void a() =>
+    // There's something we want to document here.
+    super.a();
+}
+''');
+  }
+
+  test_method_ok_returnExpression_commentsInBody() async {
+    await assertNoDiagnostics(r'''
+class A {
+  @override
+  String toString() {
+    // There's something we want to document here.
+    return super.toString();
+  }
+}
+''');
+  }
+}
diff --git a/test/rules/use_build_context_synchronously_test.dart b/test/rules/use_build_context_synchronously_test.dart
new file mode 100644
index 0000000..9e7a89b
--- /dev/null
+++ b/test/rules/use_build_context_synchronously_test.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UseBuildContextSynchronouslyTest);
+  });
+}
+
+@reflectiveTest
+class UseBuildContextSynchronouslyTest extends LintRuleTest {
+  @override
+  bool get addFlutterPackageDep => true;
+
+  @override
+  String get lintRule => 'use_build_context_synchronously';
+
+  /// Ensure we're not run in the test dir.
+  @override
+  String get testPackageRootPath => '$workspaceRootPath/lib';
+
+  /// https://github.com/dart-lang/linter/issues/3818
+  test_context_propertyAccess() async {
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+class W {
+  final BuildContext context;
+  W(this.context);
+
+  Future<void> f() async {
+    await Future.value();
+    g(this.context);
+  }
+
+  Future<void> g(BuildContext context) async {}
+}
+''', [
+      lint(157, 15),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3676
+  test_contextPassedAsNamedParam() async {
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+Future<void> foo(BuildContext context) async {
+    await Future.value();
+    bar(context: context);
+}
+
+Future<void> bar({required BuildContext context}) async {}
+''', [
+      lint(117, 21),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3700
+  test_propertyAccess_getter() async {
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+extension on BuildContext {
+  BuildContext get foo => this;
+}
+
+Future<void> f(BuildContext context) async {
+  await Future.value();
+  context.foo;
+}
+''', [
+      lint(174, 11),
+    ]);
+  }
+
+  test_propertyAccess_setter() async {
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+extension on BuildContext {
+  set foo(int x){ }
+}
+
+Future<void> f(BuildContext context) async {
+  await Future.value();
+  context.foo = 1;
+}
+''', [
+      lint(162, 11),
+    ]);
+  }
+}
diff --git a/test/rules/use_enums_test.dart b/test/rules/use_enums_test.dart
new file mode 100644
index 0000000..e2804ee
--- /dev/null
+++ b/test/rules/use_enums_test.dart
@@ -0,0 +1,384 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UseEnumsTest);
+  });
+}
+
+@reflectiveTest
+class UseEnumsTest extends LintRuleTest {
+  @override
+  String get lintRule => 'use_enums';
+
+  test_constructor_private() async {
+    await assertDiagnostics(r'''
+class A {
+  static const A a = A._(1);
+  static const A b = A._(2);
+  final int value;
+  const A._(this.value);
+}
+''', [
+      lint(6, 1),
+    ]);
+  }
+
+  test_extendsObject() async {
+    await assertDiagnostics(r'''
+class A extends Object {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+}
+''', [
+      lint(6, 1),
+    ]);
+  }
+
+  test_multiDeclaration() async {
+    await assertDiagnostics(r'''
+class A {
+  static const A a = A._(), b = A._();
+  const A._();
+}
+''', [
+      lint(6, 1),
+    ]);
+  }
+
+  test_no_lint_abstract() async {
+    await assertNoDiagnostics(r'''
+abstract class A {
+  static const A a = B();
+  static const A b = B();
+  const A();
+}
+class B extends A {
+  const B();
+}  
+''');
+  }
+
+  test_no_lint_constructor_factory() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A();
+  static const A b = A();
+  factory A.f(int index) {
+    throw '!';
+  }
+  const A();
+}
+''');
+  }
+
+  test_no_lint_constructor_named() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A.x();
+  static const A b = A.x();
+  const A.x();
+}
+''');
+  }
+
+  test_no_lint_constructor_public() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A(1);
+  static const A b = A(2);
+  final int index;
+  const A(this.index);
+}
+''');
+  }
+
+  test_no_lint_constructorUsedInConstructor() async {
+    await assertDiagnostics(r'''
+class _E {
+  static const _E a = _E();
+  static const _E b = _E();
+
+  const _E({_E e = const _E()});
+}
+''', [
+      // No lint.
+      error(HintCode.UNUSED_ELEMENT, 6, 2),
+      error(HintCode.UNUSED_FIELD, 29, 1),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+      error(HintCode.UNUSED_ELEMENT_PARAMETER, 83, 1),
+    ]);
+  }
+
+  test_no_lint_constructorUsedOutsideClass() async {
+    await assertDiagnostics(r'''
+class _E {
+  static const _E a = _E();
+  static const _E b = _E();
+
+  const _E();
+}
+
+_E get e => _E();
+''', [
+      // No lint.
+      error(HintCode.UNUSED_FIELD, 29, 1),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+    ]);
+  }
+
+  test_no_lint_extended() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+}
+class B extends A {
+  const B() : super._();
+}
+''');
+  }
+
+  test_no_lint_extends_notObject() async {
+    await assertNoDiagnostics(r'''
+class O { 
+  const O();
+}    
+class A extends O {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+}
+''');
+  }
+
+  test_no_lint_factoryAll() async {
+    await assertDiagnostics(r'''
+class _E {
+  static _E c = _E();
+  static _E d = _E();
+
+  factory _E() => c;
+}
+''', [
+      // No lint.
+      error(HintCode.UNUSED_ELEMENT, 6, 2),
+      error(HintCode.UNUSED_FIELD, 45, 1),
+    ]);
+  }
+
+  test_no_lint_factorySome() async {
+    await assertDiagnostics(r'''
+class _E {
+  static _E c0 = _E._();
+  static _E c1 = _E();
+
+  factory _E() => c0;
+  const _E._();
+}
+''', [
+      // No lint.
+      error(HintCode.UNUSED_ELEMENT, 6, 2),
+      error(HintCode.UNUSED_FIELD, 48, 2),
+    ]);
+  }
+
+  test_no_lint_implemented() async {
+    await assertDiagnostics('''
+class _E {
+  static const _E c = _E();
+  static const _E d = _E();
+
+  const _E();
+}
+class F implements _E  {}
+''', [
+      // No lint.
+      error(HintCode.UNUSED_FIELD, 29, 1),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+    ]);
+  }
+
+  test_no_lint_implements_index_field() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+  final int index = 0;
+}
+''');
+  }
+
+  test_no_lint_implements_index_getter() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+  int get index => 0;
+}
+''');
+  }
+
+  test_no_lint_implements_values_field() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+  static final List<A> values = [a, b];
+}
+''');
+  }
+
+  test_no_lint_implements_values_method() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+  static List<A> values() => [];
+}
+''');
+  }
+
+  test_no_lint_nonConstConstructor() async {
+    await assertDiagnostics('''
+class _E {
+  static final _E a = _E();
+  static final _E b = _E();
+
+  _E();
+}
+''', [
+      // No lint.
+      // todo(pq):consider relaxing the lint to flag cases w/o a const
+      // but all final fields.
+      error(HintCode.UNUSED_ELEMENT, 6, 2),
+      error(HintCode.UNUSED_FIELD, 29, 1),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+    ]);
+  }
+
+  test_no_lint_nonInstanceCreationInitialization() async {
+    await assertDiagnostics(r'''
+class _E {
+  static const _E a = _E();
+  static const _E b = a;
+
+  const _E();
+}
+''', [
+      // No lint.
+      error(HintCode.UNUSED_ELEMENT, 6, 2),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+    ]);
+  }
+
+  test_no_lint_overrides_equals() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A();
+  static const A b = A();
+  const A();
+  @override
+  bool operator ==(Object other) => false;
+}
+''');
+  }
+
+  test_no_lint_overrides_hashCode() async {
+    await assertNoDiagnostics(r'''
+class A {
+  static const A a = A();
+  static const A b = A();
+  const A();
+  @override
+  int get hashCode => 0;
+}
+''');
+  }
+
+  test_referencedFactoryConstructor() async {
+    await assertDiagnostics(r'''
+class _E {
+  static const _E c = _E();
+  static const _E d = _E();
+
+  const _E();
+
+  factory _E.withValue(int x) => c;
+}
+
+_E e = _E.withValue(0);
+''', [
+      lint(6, 2),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+    ]);
+  }
+
+  test_simple_hasPart() async {
+    newFile2('$testPackageLibPath/a.dart', '''
+part of 'test.dart';
+''');
+    await assertDiagnostics(r'''
+part 'a.dart';
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+}
+''', [
+      lint(21, 1),
+    ]);
+  }
+
+  test_simple_private() async {
+    await assertDiagnostics(r'''
+class _A {
+  static const _A a = _A();
+  static const _A b = _A();
+  const _A();
+}
+''', [
+      error(HintCode.UNUSED_ELEMENT, 6, 2),
+      error(HintCode.UNUSED_FIELD, 29, 1),
+      error(HintCode.UNUSED_FIELD, 57, 1),
+      lint(6, 2),
+    ]);
+  }
+
+  test_simple_public() async {
+    await assertDiagnostics(r'''
+class A {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+}
+''', [
+      lint(6, 1),
+    ]);
+  }
+
+  test_withMixin() async {
+    await assertDiagnostics(r'''
+class M { }    
+class A with M {
+  static const A a = A._();
+  static const A b = A._();
+  const A._();
+}
+''', [
+      lint(22, 1),
+    ]);
+  }
+}
diff --git a/test/rules/use_is_even_rather_than_modulo_test.dart b/test/rules/use_is_even_rather_than_modulo_test.dart
new file mode 100644
index 0000000..9c4b207
--- /dev/null
+++ b/test/rules/use_is_even_rather_than_modulo_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UseIsEvenRatherThanModuloTest);
+  });
+}
+
+@reflectiveTest
+class UseIsEvenRatherThanModuloTest extends LintRuleTest {
+  @override
+  String get lintRule => 'use_is_even_rather_than_modulo';
+
+  test_undefinedClass() async {
+    await assertDiagnostics(r'''
+Class tmp;
+bool a = tmp % 2 == 0;
+''', [
+      // No lint
+      error(CompileTimeErrorCode.UNDEFINED_CLASS, 0, 5),
+    ]);
+  }
+}
diff --git a/test/rules/use_late_for_private_fields_and_variables_test.dart b/test/rules/use_late_for_private_fields_and_variables_test.dart
new file mode 100644
index 0000000..84986db
--- /dev/null
+++ b/test/rules/use_late_for_private_fields_and_variables_test.dart
@@ -0,0 +1,190 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UseLateForPrivateFieldsAndVariablesTest);
+  });
+}
+
+@reflectiveTest
+class UseLateForPrivateFieldsAndVariablesTest extends LintRuleTest {
+  @override
+  String get lintRule => 'use_late_for_private_fields_and_variables';
+
+  test_instanceField_private() async {
+    await assertDiagnostics('''
+class C {
+  int? _i; // ignore: unused_field
+}
+''', [lint(17, 2)]);
+  }
+
+  test_instanceField_private_inClassWithConstConstructor() async {
+    await assertNoDiagnostics('''
+class C {
+  const C([this._i]);
+  final int? _i; // ignore: unused_field
+}
+''');
+  }
+
+  test_instanceField_private_inEnum() async {
+    await assertNoDiagnostics('''
+enum E {
+  a('');
+  const E([this._f]);
+  final String? _f; // ignore: unused_field
+}
+''');
+  }
+
+  test_instanceField_public() async {
+    await assertNoDiagnostics('''
+class C {
+  int? i;
+}
+''');
+  }
+
+  test_staticField_private_onExtension() async {
+    await assertDiagnostics('''
+extension E on int {
+  static int? _i; // ignore: unused_field
+}
+''', [lint(35, 2)]);
+  }
+
+  // TODO(srawlins): Add test_staticField_private_onClass.
+
+  test_staticField_public_onPrivateExtension() async {
+    await assertDiagnostics('''
+extension _E on int {
+  static int? i; // ignore: unused_field
+}
+''', [lint(36, 1)]);
+  }
+
+  test_staticField_public_onPublicExtension() async {
+    await assertNoDiagnostics('''
+extension E on int {
+  static int? i;
+}
+''');
+  }
+
+  test_staticField_public_onUnnamedExtension() async {
+    await assertDiagnostics('''
+extension on int {
+  static int? i; // ignore: unused_field
+}
+''', [lint(33, 1)]);
+  }
+
+  test_topLevel_assigned() async {
+    await assertDiagnostics('''
+int? _i; // ignore: unused_element
+void f() {
+  _i = 1;
+}
+''', [lint(5, 2)]);
+  }
+
+  test_topLevel_neverUsed() async {
+    await assertDiagnostics('''
+int? _i; // ignore: unused_element
+''', [lint(5, 2)]);
+  }
+
+  test_topLevel_onlyAssignedNull() async {
+    await assertNoDiagnostics('''
+int? _i; // ignore: unused_element
+void f() {
+  _i = null;
+}
+''');
+  }
+
+  test_topLevel_onlyEqualityCompared() async {
+    await assertNoDiagnostics('''
+int? _i; // ignore: unused_element
+f() {
+  _i == 1;
+}
+''');
+  }
+
+  test_topLevel_onlyNullAwareAccess() async {
+    await assertNoDiagnostics('''
+int? _i; // ignore: unused_element
+f() {
+  _i?.abs();
+}
+''');
+  }
+
+  test_topLevel_onlyNullChecked() async {
+    await assertDiagnostics('''
+int? _i; // ignore: unused_element
+f() {
+  _i!.abs();
+}
+''', [lint(5, 2)]);
+  }
+
+  test_topLevel_onlyNullChecked_beforePassedAsArgument() async {
+    await assertDiagnostics('''
+int? _i; // ignore: unused_element
+f(int i) {
+  f(_i!);
+}
+''', [lint(5, 2)]);
+  }
+
+  test_topLevel_onlyNullTest() async {
+    await assertNoDiagnostics('''
+int? _i; // ignore: unused_element
+f() {
+  if (_i != null) _i.toString();
+}
+''');
+  }
+
+  test_topLevel_passedAsArgument() async {
+    await assertNoDiagnostics('''
+int? _i;
+f(int? i) {
+  f(_i);
+}
+''');
+  }
+
+  test_useInPart() async {
+    newFile2('$testPackageRootPath/lib/lib.dart', '''
+part '$testFileName';
+
+int? _i;
+''');
+    await assertDiagnostics('''
+part of 'lib.dart';
+
+m() {
+  _i = 1;
+}
+''', [lint(24, 2)]);
+  }
+
+  test_topLevel_public() async {
+    await assertNoDiagnostics('''
+int? i;
+void f() {
+  i = 1;
+}
+''');
+  }
+}
diff --git a/test/rules/use_string_in_part_of_directives.dart b/test/rules/use_string_in_part_of_directives.dart
new file mode 100644
index 0000000..3ddca8a
--- /dev/null
+++ b/test/rules/use_string_in_part_of_directives.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UseStringInPartOfDirectivesTest);
+  });
+}
+
+@reflectiveTest
+class UseStringInPartOfDirectivesTest extends LintRuleTest {
+  @override
+  bool get addMetaPackageDep => true;
+
+  @override
+  String get lintRule => 'use_string_in_part_of_directives';
+
+  test_part_of_with_string() async {
+    newFile2('$testPackageRootPath/lib/lib.dart', '''
+part '$testFileName';
+''');
+    await assertNoDiagnostics(r'''
+part of 'lib.dart';
+''');
+  }
+
+  test_part_of_with_library_name() async {
+    newFile2('$testPackageRootPath/lib/lib.dart', '''
+library lib;
+part '$testFileName';
+''');
+    await assertDiagnostics(
+      r'''
+part of lib;
+''',
+      [
+        lint(0, 12),
+      ],
+    );
+  }
+}
diff --git a/test/rules/use_super_parameters_test.dart b/test/rules/use_super_parameters_test.dart
new file mode 100644
index 0000000..f761025
--- /dev/null
+++ b/test/rules/use_super_parameters_test.dart
@@ -0,0 +1,436 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UseSuperParametersTest);
+  });
+}
+
+@reflectiveTest
+class UseSuperParametersTest extends LintRuleTest {
+  @override
+  String get lintRule => 'use_super_parameters';
+
+  test_functionTypedFormalParameter() async {
+    await assertDiagnostics(r'''
+class A {
+  A(int f(int i));
+}
+class B extends A {
+  B(int f(int i)) : super(f);
+}
+''', [
+      lint(53, 1),
+    ]);
+  }
+
+  test_named() async {
+    await assertDiagnostics(r'''
+class A {
+  const A({int? x, int? y});
+}
+class B extends A {
+  const B({int? x, int? y}) : super(x: x, y: y);
+}
+''', [
+      lint(69, 1),
+    ]);
+  }
+
+  Future<void> test_named_oneWithNameChange() async {
+    await assertDiagnostics('''
+class A {
+  A({int? x, int? y});
+}
+class B extends A {
+  B({int? x, int? z}) : super(x: x, y: z);
+}
+''', [
+      lint(57, 1),
+    ]);
+  }
+
+  test_named_someReferencedInBody() async {
+    await assertDiagnostics(r'''
+class A {
+  A({int? x, int? y});
+}
+class B extends A {
+  B({int? x, int? y}) : super(x: x, y: y) {
+    print(x);
+  }
+}
+''', [
+      lint(57, 1, messageContains: "Convert 'y' to a super parameter"),
+    ]);
+  }
+
+  test_named_thisParameter() async {
+    await assertDiagnostics(r'''
+class A {
+  A({int? x, int? y});
+}
+class B extends A {
+  int? x;
+  B({this.x, int? y}) : super(x:x, y:y);
+}
+''', [
+      lint(67, 1, messageContains: "Convert 'y' to a super parameter."),
+    ]);
+  }
+
+  test_no_lint_forwardedOutOfOrder() async {
+    await assertNoDiagnostics(r'''
+class B {
+  final int x;
+  final int y;
+  B(this.x, this.y);
+}
+class C extends B {
+  C(int x, int y) : super(y, x);
+}
+''');
+  }
+
+  test_no_lint_invalid_fieldFormalParameter() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(int x);
+}
+class B extends A {
+  B(int x) : super(x) {
+    print(x);
+  }
+}
+''');
+  }
+
+  Future<void> test_no_lint_named_nameChange() async {
+    await assertNoDiagnostics('''
+class A {
+  A({int? x});
+}
+class B extends A {
+  B({int? y}) : super(x: y);
+}
+''');
+  }
+
+  test_no_lint_named_noSuperInvocation() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({int x = 0});
+}
+class B extends A {
+  B({int x = 1});
+}
+''');
+  }
+
+  test_no_lint_named_notGenerative() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({required int x});
+}
+class B extends A {
+  static List<B> instances = [];
+  factory B({required int x}) => instances[x];
+}
+''');
+  }
+
+  test_no_lint_named_notPassed_unreferenced() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({int x = 0});
+}
+class B extends A {
+  B({int x = 0}) : super(x: 0);
+}
+''');
+  }
+
+  test_no_lint_named_notPassed_usedInExpression() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({String x = ''});
+}
+class B extends A {
+  B({required Object x}) : super(x: x.toString());
+}
+''');
+  }
+
+  test_no_lint_named_passedAsPositional() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(String x);
+}
+class B extends A {
+  B({required String x}) : super(x);
+}
+''');
+  }
+
+  test_no_lint_nonSimpleIdentifierArg() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(int x, int y, [int? z]);
+}
+class B extends A {
+  B(int a, int b) : super(a, 2, b);
+}
+''');
+  }
+
+  test_no_lint_notAllForwarded() async {
+    await assertNoDiagnostics(r'''
+class B {
+  final int x;
+  final int y;
+  B(this.x, this.y);
+}
+class C extends B {
+  C(int x) : super(x, 0);
+}
+''');
+  }
+
+  test_no_lint_positionalReferencedInBody() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A.a(int x, int? y);
+}
+class B extends A {
+  B(int x, int? y) : super.a(x, y) {
+    print(x);
+  }
+}
+''');
+  }
+
+  test_no_lint_positionalThisParameter() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(int x, int y);
+}
+class B extends A {
+  int x;
+  B(this.x, int y) : super(x, y);
+}
+''');
+  }
+
+  test_no_lint_referencedInBody() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A.a(int x, {int? y});
+}
+class B extends A {
+  B(int x, {int? y}) : super.a(x, y: y) {
+    print(x);
+    print(y);
+  }
+}
+''');
+  }
+
+  test_no_lint_referencedInBody_named() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({int? x});
+}
+class B extends A {
+  B({int? x}) : super(x: x) {
+    print(x);
+  }
+}
+''');
+  }
+
+  test_no_lint_referencedInBody_positional() async {
+    await assertNoDiagnostics(r'''
+class A {
+  int x;
+  A(this.x);
+}
+class B extends A {
+  int y;
+  B(this.y) : super(y);
+}
+''');
+  }
+
+  test_no_lint_requiredPositional_namedInSuper() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A({int? x});
+}
+class B extends A {
+  B(int x) : super(x: x);
+}
+''');
+  }
+
+  test_no_lint_requiredPositional_noSuperInvocation() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A();
+}
+class B extends A {
+  B(int x);
+}
+''');
+  }
+
+  test_no_lint_requiredPositional_notGenerative() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(int x);
+}
+class B extends A {
+  static List<B> instances = [];
+  factory B(int x) => instances[x];
+}
+''');
+  }
+
+  test_no_lint_requiredPositional_notPassed_unreferenced() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(int x);
+}
+class B extends A {
+  B(int x) : super(0);
+}
+''');
+  }
+
+  test_no_lint_requiredPositional_notPassed_usedInExpression() async {
+    await assertNoDiagnostics(r'''
+class A {
+  A(String x);
+}
+class B extends A {
+  B(Object x) : super(x.toString());
+}
+''');
+  }
+
+  test_nonForwardingNamed() async {
+    await assertDiagnostics(r'''
+class A {
+  A(int x, {int? foo}); 
+}
+class B extends A {
+  B(int x, {int? foo}) : super(x, foo: 0); 
+}
+''', [
+      lint(59, 1),
+    ]);
+  }
+
+  test_optionalPositional_inSuper() async {
+    await assertDiagnostics(r'''
+class A {
+  A(int x, [int? y]);
+}
+class B extends A {
+  B(int x) : super(x);
+}
+''', [
+      lint(56, 1),
+    ]);
+  }
+
+  test_optionalPositional_singleSuperParameter() async {
+    await assertDiagnostics(r'''
+class A {
+  A(int x);
+}
+class B extends A {
+  B([int x = 0]) : super(x);
+}
+''', [
+      lint(46, 1),
+    ]);
+  }
+
+  /// https://github.com/dart-lang/linter/issues/3569
+  test_repeatedParam() async {
+    await assertNoDiagnostics(r'''
+class Rect {
+  Rect(this.width, this.height);
+
+  final double width;
+  final double height;
+}
+
+class Square extends Rect {
+  Square(double dimension) : super(dimension, dimension);
+}
+''');
+  }
+
+  test_requiredPositional_allConvertible() async {
+    await assertDiagnostics(r'''
+class B {
+  final int foo;
+  final int bar;
+  B(this.foo, this.bar);
+}
+class C extends B {
+  C(int foo, int bar) : super(foo, bar);
+}
+''', [
+      lint(93, 1),
+    ]);
+  }
+
+  test_requiredPositional_mixedSuperParameters() async {
+    await assertDiagnostics(r'''
+class A {
+  A(int x, {int? y});
+}
+class B extends A {
+  B(int x, int y) : super(x, y: y);
+}
+''', [
+      lint(56, 1),
+    ]);
+  }
+
+  test_requiredPositional_someConvertible() async {
+    await assertDiagnostics(r'''
+class B {
+  final int foo;
+  final int bar;
+  B(this.foo, this.bar);
+}
+class C extends B {
+  C(int baz, int foo, int bar) : super(foo, bar);
+}
+''', [
+      lint(93, 1),
+    ]);
+  }
+
+  test_requiredPositional_withNamed() async {
+    await assertDiagnostics(r'''
+class A {
+  A(int x, {int? y});
+}
+class B extends A {
+  B(int x, {int? y}) : super(x, y: y);
+}
+''', [
+      lint(56, 1),
+    ]);
+  }
+}
diff --git a/test/rules/void_checks_test.dart b/test/rules/void_checks_test.dart
new file mode 100644
index 0000000..ac10786
--- /dev/null
+++ b/test/rules/void_checks_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(VoidChecksTest);
+  });
+}
+
+@reflectiveTest
+class VoidChecksTest extends LintRuleTest {
+  @override
+  String get lintRule => 'void_checks';
+
+  test_extraPositionalArgument() async {
+    await assertDiagnostics(r'''
+missing_parameter_for_argument() {
+  void foo() {}
+  foo(0);
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS, 57, 1),
+    ]);
+  }
+
+  test_returnOfInvalidType() async {
+    await assertDiagnostics(r'''
+void bug2813() {
+  return 1;
+}
+''', [
+      // No lint
+      error(CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, 26, 1),
+    ]);
+  }
+}
diff --git a/test/unmocked_sdk_rule_test.dart b/test/unmocked_sdk_rule_test.dart
new file mode 100644
index 0000000..6a66d91
--- /dev/null
+++ b/test/unmocked_sdk_rule_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import 'rule_test.dart';
+import 'test_constants.dart';
+
+void main() {
+  group('un-mocked', () {
+    // Validate that rule tests produce the expected results when run against
+    // an un-mocked SDK.
+    for (var entry in Directory(p.join(ruleTestDir, 'unmocked')).listSync()) {
+      if (entry is! File) continue;
+
+      var ruleName = p.basenameWithoutExtension(entry.path);
+      testRule(ruleName, entry, useMockSdk: false);
+    }
+  });
+}
diff --git a/test/util/rule_debug.dart b/test/util/rule_debug.dart
index 8102040..638a3e6 100644
--- a/test/util/rule_debug.dart
+++ b/test/util/rule_debug.dart
@@ -19,7 +19,7 @@
 ///     dart test -N valid_regexps
 ///
 void main(List<String> args) {
-  var ruleName = args[0];
+  var ruleName = args.first;
   var dir = Directory(ruleTestDir).absolute;
   testRule(ruleName, File(p.join(dir.path, '$ruleName.dart')));
 }
diff --git a/test/utils_test.dart b/test/utils_test.dart
index 82b3c64..a93aa33 100644
--- a/test/utils_test.dart
+++ b/test/utils_test.dart
@@ -25,6 +25,7 @@
       'async',
     ], isValidPackageName, isTrue);
     testEach([
+      'break', // reserved word
       'fOO',
       'foo_',
       'foo_Bar',
diff --git a/test/validate_headers_test.dart b/test/validate_headers_test.dart
index 0f9d055..ee49f03 100644
--- a/test/validate_headers_test.dart
+++ b/test/validate_headers_test.dart
@@ -8,9 +8,15 @@
 
 void main() {
   group('check for copyright headers', () {
+    test('... in bin', () async {
+      await validate('bin');
+    });
     test('... in lib', () async {
       await validate('lib');
     });
+    test('... in test', () async {
+      await validate('test');
+    });
     test('... in tool', () async {
       await validate('tool');
     });
diff --git a/test/validate_no_rule_description_references.dart b/test/validate_no_rule_description_references.dart
new file mode 100644
index 0000000..063d365
--- /dev/null
+++ b/test/validate_no_rule_description_references.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:linter/src/analyzer.dart';
+import 'package:linter/src/rules.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('check for rule message descriptions in tests:', () {
+    registerLintRules();
+    for (var rule in Analyzer.facade.registeredRules) {
+      test(rule.name, () async {
+        var result = Process.runSync('grep', ['-R', rule.description, 'test']);
+        expect(result.stdout, isEmpty,
+            reason:
+                'Tests that hardcode descriptions make lint messages hard to evolve.');
+      });
+    }
+  });
+}
diff --git a/test/validate_rule_description_format_test.dart b/test/validate_rule_description_format_test.dart
new file mode 100644
index 0000000..8b7c895
--- /dev/null
+++ b/test/validate_rule_description_format_test.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/lint/registry.dart';
+import 'package:test/test.dart';
+
+void main() {
+  const keywords = [
+    'GOOD',
+    'BAD',
+    'NOTE',
+    'DEPRECATED',
+    'EXCEPTION',
+    'EXCEPTIONS',
+  ];
+
+  group('rule doc format', () {
+    var rules = Registry.ruleRegistry.rules;
+    test('(setup)', () {
+      expect(rules, isNotEmpty,
+          reason:
+              'Ensure `registerLintRules()` is called before running this suite.');
+    });
+
+    group('description - trailing periods', () {
+      for (var rule in rules) {
+        test('`${rule.name}` description', () {
+          expect(rule.description.endsWith('.'), isTrue,
+              reason:
+                  "Rule description for ${rule.name} should end with a '.'");
+        });
+      }
+    });
+
+    group('details - no leading whitespace', () {
+      for (var rule in rules) {
+        test('`${rule.name}` details', () {
+          expect(rule.details.startsWith(RegExp(r'\s+')), isFalse,
+              reason:
+                  'Rule details for ${rule.name} should not have leading whitespace.');
+        });
+      }
+    });
+
+    group('details - bad first', () {
+      for (var rule in rules) {
+        test('`${rule.name}` bad example first', () {
+          var details = rule.details;
+          var lines = details.split('\n');
+          var hasGood = false;
+          for (var line in lines) {
+            if (line.startsWith('**BAD:**')) {
+              if (hasGood) {
+                fail(
+                    'Rule details for ${rule.name} should have the BAD example before the GOOD one.');
+              }
+              break;
+            } else if (line.startsWith('**GOOD:**')) {
+              hasGood = true;
+            }
+          }
+        });
+      }
+    });
+
+    group('details - colon inside stars', () {
+      for (var rule in rules) {
+        test('`${rule.name}` colon inside stars', () {
+          var details = rule.details;
+          var lines = details.split('\n');
+
+          for (var line in lines) {
+            for (var keyword in keywords) {
+              var withStars = '**$keyword**';
+              if (line.contains(withStars)) {
+                fail(
+                    'Rule details for ${rule.name} should have **$keyword:**, put the colon inside the stars.');
+              }
+            }
+          }
+        });
+      }
+    });
+
+    group('details - upper case keywords', () {
+      for (var rule in rules) {
+        test('`${rule.name}` upper case keywords', () {
+          var details = rule.details;
+          var lines = details.split('\n');
+
+          for (var line in lines) {
+            for (var keyword in keywords) {
+              var withStars = '**$keyword:**';
+              if (line.toLowerCase().contains(withStars.toLowerCase()) &&
+                  !line.contains(withStars)) {
+                fail(
+                    'Rule details for ${rule.name} should have $withStars in upper case.');
+              }
+            }
+          }
+        });
+      }
+    });
+  });
+}
diff --git a/test/verify_reflective_test_suites.dart b/test/verify_reflective_test_suites.dart
new file mode 100644
index 0000000..41c2eaa
--- /dev/null
+++ b/test/verify_reflective_test_suites.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+void main() {
+  group('check reflective test suites', () {
+    group('rules', () {
+      var testDirPath = PhysicalResourceProvider.INSTANCE.pathContext
+          .absolute('test', 'rules');
+      _VerifyTests(testDirPath).build();
+    });
+  });
+}
+
+/// Helper class to test that [testAllFileName] files are properly set up.
+/// (Cribbed from `analyzer_utilities`.)
+class _VerifyTests {
+  final String testDirPath;
+
+  _VerifyTests(this.testDirPath);
+
+  String get testAllFileName => 'all.dart';
+
+  /// Build tests.
+  void build() {
+    var provider = PhysicalResourceProvider.INSTANCE;
+    var collection = AnalysisContextCollection(
+        resourceProvider: provider, includedPaths: <String>[testDirPath]);
+    var contexts = collection.contexts;
+    if (contexts.length != 1) {
+      fail('The test directory contains multiple analysis contexts.');
+    }
+
+    _buildTestsIn(contexts.first.currentSession, testDirPath,
+        provider.getFolder(testDirPath));
+  }
+
+  void _buildTestsIn(
+      AnalysisSession session, String testDirPath, Folder directory) {
+    var testFileNames = <String>[];
+    File? testAllFile;
+    var children = directory.getChildren();
+    children
+        .sort((first, second) => first.shortName.compareTo(second.shortName));
+    for (var child in children) {
+      if (child is Folder) {
+        if (child.getChildAssumingFile(testAllFileName).exists) {
+          testFileNames.add('${child.shortName}/$testAllFileName');
+        }
+        _buildTestsIn(session, testDirPath, child);
+      } else if (child is File) {
+        var name = child.shortName;
+        if (name == testAllFileName) {
+          testAllFile = child;
+        } else if (name.endsWith('_test.dart')) {
+          testFileNames.add(name);
+        }
+      }
+    }
+    var relativePath = path.relative(directory.path, from: testDirPath);
+    test(relativePath, () async {
+      if (testFileNames.isEmpty) return;
+      if (testAllFile == null) return;
+
+      var result = session.getParsedUnit(testAllFile.path);
+      if (result is! ParsedUnitResult) {
+        fail('Could not parse ${testAllFile.path}');
+      }
+      var importedFiles = <String>[];
+      for (var directive in result.unit.directives) {
+        if (directive is ImportDirective) {
+          var uri = directive.uri.stringValue;
+          if (uri == null) {
+            fail('Invalid URI: $directive');
+          }
+          importedFiles.add(uri);
+        }
+      }
+      var missingFiles = <String>[];
+      for (var testFileName in testFileNames) {
+        if (!importedFiles.contains(testFileName)) {
+          missingFiles.add(testFileName);
+        }
+      }
+      if (missingFiles.isNotEmpty) {
+        fail(
+            'Tests missing from "$testDirPath/$testAllFileName": ${missingFiles.join(', ')}');
+      }
+      var extraImports = <String>[];
+      for (var importedFile in importedFiles) {
+        if (!testFileNames.contains(importedFile)) {
+          extraImports.add(importedFile);
+        }
+      }
+      if (extraImports.isNotEmpty) {
+        fail(
+            'Extra tests in "$testDirPath/$testAllFileName": ${extraImports.join(', ')}');
+      }
+    });
+  }
+}
diff --git a/test_data/analysis_options.yaml b/test_data/analysis_options.yaml
new file mode 100644
index 0000000..13e5d8f
--- /dev/null
+++ b/test_data/analysis_options.yaml
@@ -0,0 +1 @@
+# We need this file to prevent using the one from the package root.
diff --git a/test_data/integration/always_require_non_null_named_parameters/.dart_tool/package_config.json b/test_data/integration/always_require_non_null_named_parameters/.dart_tool/package_config.json
new file mode 100644
index 0000000..01cd475
--- /dev/null
+++ b/test_data/integration/always_require_non_null_named_parameters/.dart_tool/package_config.json
@@ -0,0 +1,14 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "meta",
+      "rootUri": "../../../mock_packages/meta/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/always_use_package_imports/_packages b/test_data/integration/always_use_package_imports/_packages
deleted file mode 100644
index a484002..0000000
--- a/test_data/integration/always_use_package_imports/_packages
+++ /dev/null
@@ -1,2 +0,0 @@
-sample_project:lib/
-p6:../p6/lib/
diff --git a/test_data/integration/avoid_relative_lib_imports/.dart_tool/package_config.json b/test_data/integration/avoid_relative_lib_imports/.dart_tool/package_config.json
new file mode 100644
index 0000000..0a2d277
--- /dev/null
+++ b/test_data/integration/avoid_relative_lib_imports/.dart_tool/package_config.json
@@ -0,0 +1,14 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "p6",
+      "rootUri": "../../p6/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/avoid_relative_lib_imports/_packages b/test_data/integration/avoid_relative_lib_imports/_packages
deleted file mode 100644
index 19cb138..0000000
--- a/test_data/integration/avoid_relative_lib_imports/_packages
+++ /dev/null
@@ -1 +0,0 @@
-p6:../p6/lib/
diff --git a/test_data/integration/avoid_renaming_method_parameters/.dart_tool/package_config.json b/test_data/integration/avoid_renaming_method_parameters/.dart_tool/package_config.json
new file mode 100644
index 0000000..b3c563d
--- /dev/null
+++ b/test_data/integration/avoid_renaming_method_parameters/.dart_tool/package_config.json
@@ -0,0 +1,14 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "avoid_renaming_method_parameters",
+      "rootUri": "../",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/avoid_renaming_method_parameters/lib/a.dart b/test_data/integration/avoid_renaming_method_parameters/lib/a.dart
index 73d28aa..00d5c1d 100644
--- a/test_data/integration/avoid_renaming_method_parameters/lib/a.dart
+++ b/test_data/integration/avoid_renaming_method_parameters/lib/a.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N avoid_renaming_method_parameters`
 
+// ignore_for_file: unused_element
+
 abstract class A {
   m1();
   m2(a);
diff --git a/test_data/integration/cancel_subscriptions/cancel_subscriptions_null_safety.dart b/test_data/integration/cancel_subscriptions/cancel_subscriptions_null_safety.dart
new file mode 100644
index 0000000..00775f2
--- /dev/null
+++ b/test_data/integration/cancel_subscriptions/cancel_subscriptions_null_safety.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+class D {
+  StreamSubscription? _subscriptionD; // OK
+  void init(Stream stream) {
+    _subscriptionD = stream.listen((_) {});
+  }
+  void _cancel() {
+    if (_subscriptionD != null) {
+      _subscriptionD!.cancel();
+      _subscriptionD = null;
+    }
+  }
+}
diff --git a/test_data/integration/depend_on_referenced_packages/.dart_tool/package_config.json b/test_data/integration/depend_on_referenced_packages/.dart_tool/package_config.json
new file mode 100644
index 0000000..9c2ad7f
--- /dev/null
+++ b/test_data/integration/depend_on_referenced_packages/.dart_tool/package_config.json
@@ -0,0 +1,38 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "flutter_gen",
+      "rootUri": "../vendor/flutter_gen/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "public_dep",
+      "rootUri": "../vendor/public_dep/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "private_dep",
+      "rootUri": "../vendor/private_dep/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "transitive_dep",
+      "rootUri": "../vendor/transitive_dep/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "sample_project",
+      "rootUri": "../",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/depend_on_referenced_packages/_packages b/test_data/integration/depend_on_referenced_packages/_packages
deleted file mode 100644
index 0aa8f51..0000000
--- a/test_data/integration/depend_on_referenced_packages/_packages
+++ /dev/null
@@ -1,4 +0,0 @@
-sample_project:lib/
-public_dep:vendor/public_dep/
-private_dep:vendor/private_dep/
-transitive_dep:vendor/transitive_dep/
diff --git a/test_data/integration/depend_on_referenced_packages/lib/public.dart b/test_data/integration/depend_on_referenced_packages/lib/public.dart
index e0d477c..8cb6b34 100644
--- a/test_data/integration/depend_on_referenced_packages/lib/public.dart
+++ b/test_data/integration/depend_on_referenced_packages/lib/public.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:flutter_gen/gen.dart'; // OK
+
 import 'package:sample_project/sample_project.dart'; // OK
 import 'package:public_dep/public_dep.dart'; // OK
 import 'package:private_dep/private_dep.dart'; // LINT
diff --git a/test_data/integration/depend_on_referenced_packages/vendor/flutter_gen/lib/gen.dart b/test_data/integration/depend_on_referenced_packages/vendor/flutter_gen/lib/gen.dart
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test_data/integration/depend_on_referenced_packages/vendor/flutter_gen/lib/gen.dart
diff --git a/test_data/integration/depend_on_referenced_packages/vendor/private_dep/private_dep.dart b/test_data/integration/depend_on_referenced_packages/vendor/private_dep/lib/private_dep.dart
similarity index 100%
rename from test_data/integration/depend_on_referenced_packages/vendor/private_dep/private_dep.dart
rename to test_data/integration/depend_on_referenced_packages/vendor/private_dep/lib/private_dep.dart
diff --git a/test_data/integration/depend_on_referenced_packages/vendor/public_dep/public_dep.dart b/test_data/integration/depend_on_referenced_packages/vendor/public_dep/lib/public_dep.dart
similarity index 100%
rename from test_data/integration/depend_on_referenced_packages/vendor/public_dep/public_dep.dart
rename to test_data/integration/depend_on_referenced_packages/vendor/public_dep/lib/public_dep.dart
diff --git a/test_data/integration/depend_on_referenced_packages/vendor/transitive_dep/transitive_dep.dart b/test_data/integration/depend_on_referenced_packages/vendor/transitive_dep/lib/transitive_dep.dart
similarity index 100%
rename from test_data/integration/depend_on_referenced_packages/vendor/transitive_dep/transitive_dep.dart
rename to test_data/integration/depend_on_referenced_packages/vendor/transitive_dep/lib/transitive_dep.dart
diff --git a/test_data/integration/file_names/non-strict.css.dart b/test_data/integration/file_names/non-strict.css.dart
deleted file mode 100644
index f2cd6e7..0000000
--- a/test_data/integration/file_names/non-strict.css.dart
+++ /dev/null
@@ -1,3 +0,0 @@
-// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
diff --git a/test_data/integration/p4/.dart_tool/package_config.json b/test_data/integration/p4/.dart_tool/package_config.json
new file mode 100644
index 0000000..a9b9e79
--- /dev/null
+++ b/test_data/integration/p4/.dart_tool/package_config.json
@@ -0,0 +1,14 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "p4",
+      "rootUri": "../",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/p4/_packages b/test_data/integration/p4/_packages
deleted file mode 100644
index f594b61..0000000
--- a/test_data/integration/p4/_packages
+++ /dev/null
@@ -1 +0,0 @@
-p4:lib/
diff --git a/test_data/integration/p5/_packages b/test_data/integration/p5/_packages
deleted file mode 100644
index 6abf930..0000000
--- a/test_data/integration/p5/_packages
+++ /dev/null
@@ -1 +0,0 @@
-p6:../p6/lib/
\ No newline at end of file
diff --git a/test_data/integration/p5/p5.dart b/test_data/integration/p5/p5.dart
deleted file mode 100644
index 0f84da8..0000000
--- a/test_data/integration/p5/p5.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:p6/p6_lib.dart';
-
-/// Main.
-void main() {
-  p6_lib();
-}
diff --git a/test_data/integration/prefer_const_constructors/lintconfig.yaml b/test_data/integration/prefer_const_constructors/lintconfig.yaml
new file mode 100644
index 0000000..2393f88
--- /dev/null
+++ b/test_data/integration/prefer_const_constructors/lintconfig.yaml
@@ -0,0 +1,2 @@
+rules:
+  - prefer_const_constructors
diff --git a/test_data/integration/prefer_const_constructors/src/a.dart b/test_data/integration/prefer_const_constructors/src/a.dart
new file mode 100644
index 0000000..fecb484
--- /dev/null
+++ b/test_data/integration/prefer_const_constructors/src/a.dart
@@ -0,0 +1,3 @@
+class A {
+  const A();
+}
diff --git a/test_data/integration/prefer_const_constructors/src/b.dart b/test_data/integration/prefer_const_constructors/src/b.dart
new file mode 100644
index 0000000..6077291
--- /dev/null
+++ b/test_data/integration/prefer_const_constructors/src/b.dart
@@ -0,0 +1,10 @@
+import 'a.dart' deferred as alib;
+
+class B {
+  const B();
+}
+
+void f() {
+  var a = alib.A(); // OK
+  var b = B(); // LINT
+}
diff --git a/test_data/integration/prefer_const_constructors_in_immutables/.dart_tool/package_config.json b/test_data/integration/prefer_const_constructors_in_immutables/.dart_tool/package_config.json
new file mode 100644
index 0000000..01cd475
--- /dev/null
+++ b/test_data/integration/prefer_const_constructors_in_immutables/.dart_tool/package_config.json
@@ -0,0 +1,14 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "meta",
+      "rootUri": "../../../mock_packages/meta/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/prefer_relative_imports/.dart_tool/package_config.json b/test_data/integration/prefer_relative_imports/.dart_tool/package_config.json
new file mode 100644
index 0000000..4a38662
--- /dev/null
+++ b/test_data/integration/prefer_relative_imports/.dart_tool/package_config.json
@@ -0,0 +1,26 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "sample_project",
+      "rootUri": "../",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "p6",
+      "rootUri": "../../p6/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "internal_path_package",
+      "rootUri": "../vendor/internal_path_package/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/prefer_relative_imports/_packages b/test_data/integration/prefer_relative_imports/_packages
deleted file mode 100644
index dfb5317..0000000
--- a/test_data/integration/prefer_relative_imports/_packages
+++ /dev/null
@@ -1,3 +0,0 @@
-sample_project:lib/
-p6:../p6/lib/
-internal_path_package:vendor/internal_path_package/lib/
diff --git a/test_data/integration/public_member_api_docs/analysis_options.yaml b/test_data/integration/public_member_api_docs/analysis_options.yaml
index cb1a1b3..556c939 100644
--- a/test_data/integration/public_member_api_docs/analysis_options.yaml
+++ b/test_data/integration/public_member_api_docs/analysis_options.yaml
@@ -1,6 +1,3 @@
-analyzer:
-  enable-experiment:
-    - nonfunction-type-aliases
 linter:
   rules:
     - public_member_api_docs
diff --git a/test_data/integration/secure_pubspec_urls/pubspec.yaml b/test_data/integration/secure_pubspec_urls/pubspec.yaml
new file mode 100644
index 0000000..7c3a0a7
--- /dev/null
+++ b/test_data/integration/secure_pubspec_urls/pubspec.yaml
@@ -0,0 +1,33 @@
+name: fancy
+description: Fancy pre-built things.
+version: 1.1.1
+homepage: http://github.com/dart-lang/linter # lint
+repository: http://github.com/dart-lang/linter # lint
+issue_tracker: http://github.com/dart-lang/linter/issues # lint
+
+environment:
+  sdk: ">=2.15.2 <3.0.0"
+
+dependencies:
+  transmogrify:
+    hosted:
+      name: transmogrify
+      url: http://some-package-server.com # lint
+    version: ^1.0.0
+
+dev_dependencies:
+  kittens_private2:
+    hosted: http://my-package-server.com #lint
+
+
+dependency_overrides:
+  kittens:
+    git: http://github.com/munificent/kittens.git # lint, needs fix in analyzer
+  kittens2:
+    git:
+      url: http://github.com/munificent/kittens2.git # lint
+      ref: main
+  kittens3:
+    git:
+      url: git://github.com/munificent/kittens2.git # lint
+      ref: main
diff --git a/test_data/integration/synthetic/synthetic.dart b/test_data/integration/synthetic/synthetic.dart
deleted file mode 100644
index 0f399c6..0000000
--- a/test_data/integration/synthetic/synthetic.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-// Verifies that synthetic nodes don't get lints.
-// The simple identifier synthesized after `XXX` below triggers the
-// `non_constant_identifier_names` lint.
-// See: https://github.com/dart-lang/linter/issues/193
-class C <E>{ }
-C<int>;
diff --git a/test_data/integration/unnecessary_library_directive/lib_with_part.dart b/test_data/integration/unnecessary_library_directive/lib_with_part.dart
new file mode 100644
index 0000000..e01db3d
--- /dev/null
+++ b/test_data/integration/unnecessary_library_directive/lib_with_part.dart
@@ -0,0 +1,5 @@
+// Parts may still use library names. No be safe, we don't lint those
+// libraries – even though using library names itself is discouraged.
+library unneeded_library_directive;
+
+part 'lib_with_part_part.dart';
diff --git a/test_data/integration/unnecessary_library_directive/lib_with_part_part.dart b/test_data/integration/unnecessary_library_directive/lib_with_part_part.dart
new file mode 100644
index 0000000..6939070
--- /dev/null
+++ b/test_data/integration/unnecessary_library_directive/lib_with_part_part.dart
@@ -0,0 +1,3 @@
+// Parts may still use library names. No be safe, we don't lint those
+// libraries – even though using library names itself is discouraged.
+part of 'lib_with_part.dart';
diff --git a/test_data/integration/unnecessary_library_directive/with_annotation.dart b/test_data/integration/unnecessary_library_directive/with_annotation.dart
new file mode 100644
index 0000000..163dd62
--- /dev/null
+++ b/test_data/integration/unnecessary_library_directive/with_annotation.dart
@@ -0,0 +1,2 @@
+/// This is a nice library
+library with_annotation; // OK
diff --git a/test_data/integration/unnecessary_library_directive/with_annotation_and_doc_comment.dart b/test_data/integration/unnecessary_library_directive/with_annotation_and_doc_comment.dart
new file mode 100644
index 0000000..cf80414
--- /dev/null
+++ b/test_data/integration/unnecessary_library_directive/with_annotation_and_doc_comment.dart
@@ -0,0 +1,5 @@
+/// A doc comment, too!
+@visibleForTesting
+library with_both; // OK
+
+import 'package:meta/meta.dart';
diff --git a/test_data/integration/unnecessary_library_directive/with_doc_comment.dart b/test_data/integration/unnecessary_library_directive/with_doc_comment.dart
new file mode 100644
index 0000000..0d06156
--- /dev/null
+++ b/test_data/integration/unnecessary_library_directive/with_doc_comment.dart
@@ -0,0 +1,4 @@
+@visibleForTesting
+library with_annotation; // OK
+
+import 'package:meta/meta.dart';
diff --git a/test_data/integration/use_build_context_synchronously/.dart_tool/package_config.json b/test_data/integration/use_build_context_synchronously/.dart_tool/package_config.json
new file mode 100644
index 0000000..6a297fb
--- /dev/null
+++ b/test_data/integration/use_build_context_synchronously/.dart_tool/package_config.json
@@ -0,0 +1,14 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "flutter",
+      "rootUri": "../../../mock_packages/flutter/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/integration/use_build_context_synchronously/lib/unmigrated.dart b/test_data/integration/use_build_context_synchronously/lib/unmigrated.dart
index c161314..7c83ad1 100644
--- a/test_data/integration/use_build_context_synchronously/lib/unmigrated.dart
+++ b/test_data/integration/use_build_context_synchronously/lib/unmigrated.dart
@@ -4,6 +4,7 @@
 
 // @dart=2.9
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
 import 'migrated.dart';
@@ -20,3 +21,36 @@
   await Future<void>.delayed(Duration());
   Navigator.of(context).pushNamed('routeName'); // LINT
 }
+
+void anonymousFunction(BuildContext context) async {
+  final anon = () async {
+    await Future<void>.delayed(Duration(seconds: 1));
+  };
+  await Navigator.of(context).pushNamed('routeName'); // OK
+}
+
+void anonymousExpressionFunction(BuildContext context) async {
+  final anon = () async => await Future<void>.delayed(Duration());
+  await Navigator.of(context).pushNamed('routeName'); // OK
+}
+
+void widgetCallbacks(BuildContext context) async {
+  final widget = _Button(
+    onTap: () async {
+      await Future<void>.delayed(Duration());
+    },
+  );
+  // Build complex widget piece by piece...
+  f(context); // OK
+}
+
+class _Button extends StatelessWidget {
+  const _Button({Key key, this.onTap}) : super(key: key);
+
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}
\ No newline at end of file
diff --git a/test_data/mock_packages/flutter/lib/foundation.dart b/test_data/mock_packages/flutter/lib/foundation.dart
index 5b201e6..a3b694c 100644
--- a/test_data/mock_packages/flutter/lib/foundation.dart
+++ b/test_data/mock_packages/flutter/lib/foundation.dart
@@ -11,6 +11,7 @@
 //        required,
 //        visibleForTesting;
 
+export 'src/foundation/constants.dart';
 export 'src/foundation/diagnostics.dart';
 export 'src/foundation/key.dart';
 export 'src/foundation/platform.dart';
diff --git a/test_data/mock_packages/flutter/lib/material.dart b/test_data/mock_packages/flutter/lib/material.dart
new file mode 100644
index 0000000..674005e
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/material.dart
@@ -0,0 +1,7 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export 'src/material/button.dart';
+export 'src/material/scaffold.dart';
+export 'widgets.dart';
diff --git a/test_data/mock_packages/flutter/lib/painting.dart b/test_data/mock_packages/flutter/lib/painting.dart
new file mode 100644
index 0000000..5815855
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/painting.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/painting/box_decoration.dart';
diff --git a/test_data/mock_packages/flutter/lib/rendering.dart b/test_data/mock_packages/flutter/lib/rendering.dart
new file mode 100644
index 0000000..b2b89c6
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/rendering.dart
@@ -0,0 +1,5 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export 'src/rendering/flex.dart';
diff --git a/test_data/mock_packages/flutter/lib/src/foundation/constants.dart b/test_data/mock_packages/flutter/lib/src/foundation/constants.dart
new file mode 100644
index 0000000..9ea62f8
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/src/foundation/constants.dart
@@ -0,0 +1,5 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const bool kDebugMode = true;
\ No newline at end of file
diff --git a/test_data/mock_packages/flutter/lib/src/material/button.dart b/test_data/mock_packages/flutter/lib/src/material/button.dart
new file mode 100644
index 0000000..1603ba8
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/src/material/button.dart
@@ -0,0 +1,14 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+
+class RawMaterialButton extends StatefulWidget {
+  final Widget? child;
+  final VoidCallback? onPressed;
+
+  const RawMaterialButton({ Key? key, required this.onPressed, this.child });
+}
diff --git a/test_data/mock_packages/flutter/lib/src/material/scaffold.dart b/test_data/mock_packages/flutter/lib/src/material/scaffold.dart
new file mode 100644
index 0000000..181ae90
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/src/material/scaffold.dart
@@ -0,0 +1,12 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+
+class Scaffold extends StatefulWidget {
+  const Scaffold({
+    Key key,
+    Widget body,
+  });
+}
diff --git a/test_data/integration/file_names/a-b.dart b/test_data/mock_packages/flutter/lib/src/painting/box_decoration.dart
similarity index 62%
rename from test_data/integration/file_names/a-b.dart
rename to test_data/mock_packages/flutter/lib/src/painting/box_decoration.dart
index babf863..881503b 100644
--- a/test_data/integration/file_names/a-b.dart
+++ b/test_data/mock_packages/flutter/lib/src/painting/box_decoration.dart
@@ -1,3 +1,5 @@
-// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
+class BoxDecoration {}
diff --git a/test_data/mock_packages/flutter/lib/src/rendering/flex.dart b/test_data/mock_packages/flutter/lib/src/rendering/flex.dart
new file mode 100644
index 0000000..d1c1e8e
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/src/rendering/flex.dart
@@ -0,0 +1,25 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+enum CrossAxisAlignment {
+  start,
+  end,
+  center,
+  stretch,
+  baseline,
+}
+
+enum MainAxisAlignment {
+  start,
+  end,
+  center,
+  spaceBetween,
+  spaceAround,
+  spaceEvenly,
+}
+
+enum MainAxisSize {
+  min,
+  max,
+}
diff --git a/test_data/mock_packages/flutter/lib/src/widgets/basic.dart b/test_data/mock_packages/flutter/lib/src/widgets/basic.dart
index a48adf8..72daf6d 100644
--- a/test_data/mock_packages/flutter/lib/src/widgets/basic.dart
+++ b/test_data/mock_packages/flutter/lib/src/widgets/basic.dart
@@ -2,8 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/rendering.dart';
+
 import 'framework.dart';
 
+export 'package:flutter/rendering.dart';
+
 class Align extends SingleChildRenderObjectWidget {
   /// How to align the child.
   ///
@@ -118,6 +122,14 @@
   });
 }
 
+class SizedBox extends SingleChildRenderObjectWidget {
+  const SizedBox({Key? key, double? width, double? height, Widget? child});
+  const SizedBox.expand({Key? key, Widget? child});
+  const SizedBox.shrink({Key? key, Widget? child});
+  SizedBox.fromSize({Key? key, Widget? child, Size? size});
+  const SizedBox.square({Key? key, Widget? child, double? dimension});
+}
+
 class Transform extends SingleChildRenderObjectWidget {
   const Transform({
     Key key,
diff --git a/test_data/mock_packages/flutter/lib/src/widgets/framework.dart b/test_data/mock_packages/flutter/lib/src/widgets/framework.dart
index 4f608da..0fb8f85 100644
--- a/test_data/mock_packages/flutter/lib/src/widgets/framework.dart
+++ b/test_data/mock_packages/flutter/lib/src/widgets/framework.dart
@@ -9,6 +9,7 @@
 
 abstract class BuildContext {
   Widget get widget;
+  bool get mounted;
   RenderObject? findRenderObject();
 }
 
diff --git a/test_data/mock_packages/flutter/lib/src/widgets/text.dart b/test_data/mock_packages/flutter/lib/src/widgets/text.dart
new file mode 100644
index 0000000..a052b28
--- /dev/null
+++ b/test_data/mock_packages/flutter/lib/src/widgets/text.dart
@@ -0,0 +1,113 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+class DefaultTextStyle extends StatelessWidget {
+  DefaultTextStyle({Widget child});
+}
+
+class Text extends StatelessWidget {
+  /// The text to display.
+  ///
+  /// This will be null if a [textSpan] is provided instead.
+  final String data;
+
+  /// If non-null, the style to use for this text.
+  ///
+  /// If the style's "inherit" property is true, the style will be merged with
+  /// the closest enclosing [DefaultTextStyle]. Otherwise, the style will
+  /// replace the closest enclosing [DefaultTextStyle].
+  final TextStyle style;
+
+  /// How the text should be aligned horizontally.
+  final TextAlign textAlign;
+
+  /// The directionality of the text.
+  ///
+  /// This decides how [textAlign] values like [TextAlign.start] and
+  /// [TextAlign.end] are interpreted.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the [data] is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// Defaults to the ambient [Directionality], if any.
+  final TextDirection textDirection;
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
+  final bool softWrap;
+
+  /// How visual overflow should be handled.
+  final TextOverflow overflow;
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  ///
+  /// The value given to the constructor as textScaleFactor. If null, will
+  /// use the [MediaQueryData.textScaleFactor] obtained from the ambient
+  /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
+  final double textScaleFactor;
+
+  /// An optional maximum number of lines for the text to span, wrapping if necessary.
+  /// If the text exceeds the given number of lines, it will be truncated according
+  /// to [overflow].
+  ///
+  /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
+  /// edge of the box.
+  ///
+  /// If this is null, but there is an ambient [DefaultTextStyle] that specifies
+  /// an explicit number for its [DefaultTextStyle.maxLines], then the
+  /// [DefaultTextStyle] value will take precedence. You can use a [RichText]
+  /// widget directly to entirely override the [DefaultTextStyle].
+  final int maxLines;
+
+  /// An alternative semantics label for this text.
+  ///
+  /// If present, the semantics of this widget will contain this value instead
+  /// of the actual text. This will overwrite any of the semantics labels applied
+  /// directly to the [TextSpan]s.
+  ///
+  /// This is useful for replacing abbreviations or shorthands with the full
+  /// text value:
+  ///
+  /// ```dart
+  /// Text(r'$$', semanticsLabel: 'Double dollars')
+  /// ```
+  final String semanticsLabel;
+
+  /// {@macro flutter.dart:ui.text.TextWidthBasis}
+  final TextWidthBasis textWidthBasis;
+
+  /// Creates a text widget.
+  ///
+  /// If the [style] argument is null, the text will use the style from the
+  /// closest enclosing [DefaultTextStyle].
+  ///
+  /// The [data] parameter must not be null.
+  const Text(
+    this.data, {
+    Key key,
+    this.style,
+    this.textAlign,
+    this.textDirection,
+    this.softWrap,
+    this.overflow,
+    this.textScaleFactor,
+    this.maxLines,
+    this.semanticsLabel,
+    this.textWidthBasis,
+  }) : super(key: key);
+}
diff --git a/test_data/mock_packages/flutter/lib/widgets.dart b/test_data/mock_packages/flutter/lib/widgets.dart
index 2d15919..09ff515 100644
--- a/test_data/mock_packages/flutter/lib/widgets.dart
+++ b/test_data/mock_packages/flutter/lib/widgets.dart
@@ -7,3 +7,4 @@
 export 'src/widgets/framework.dart';
 export 'src/widgets/navigator.dart';
 export 'src/widgets/placeholder.dart';
+export 'src/widgets/text.dart';
diff --git a/test_data/rules/.dart_tool/package_config.json b/test_data/rules/.dart_tool/package_config.json
new file mode 100644
index 0000000..c35b162
--- /dev/null
+++ b/test_data/rules/.dart_tool/package_config.json
@@ -0,0 +1,32 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "fixnum",
+      "rootUri": "../../mock_packages/fixnum/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "flutter",
+      "rootUri": "../../mock_packages/flutter/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "meta",
+      "rootUri": "../../mock_packages/meta/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    },
+    {
+      "name": "test_api",
+      "rootUri": "../../mock_packages/test_api/",
+      "packageUri": "lib/",
+      "languageVersion": "2.17"
+    }
+  ],
+  "generated": "2022-06-30T01:27:31.576781Z",
+  "generator": "pub",
+  "generatorVersion": "2.17.0"
+}
diff --git a/test_data/rules/.mock_packages b/test_data/rules/.mock_packages
deleted file mode 100644
index fe1c463..0000000
--- a/test_data/rules/.mock_packages
+++ /dev/null
@@ -1,4 +0,0 @@
-fixnum:../mock_packages/fixnum/lib/
-flutter:../mock_packages/flutter/lib/
-meta:../mock_packages/meta/lib/
-test_api:../mock_packages/test_api/lib/
diff --git a/test_data/rules/always_declare_return_types.dart b/test_data/rules/always_declare_return_types.dart
index 9383323..af474b3 100644
--- a/test_data/rules/always_declare_return_types.dart
+++ b/test_data/rules/always_declare_return_types.dart
@@ -31,7 +31,7 @@
   set speed(int ms) {} //OK
 }
 
-abstract class MyList<E> extends List<E> {
+abstract class MyList<E> implements List<E> {
   @override
   operator []=(int index, E value) //OK: #300
   {
diff --git a/test_data/rules/always_specify_types.dart b/test_data/rules/always_specify_types.dart
index a8ee4da..92c336e 100644
--- a/test_data/rules/always_specify_types.dart
+++ b/test_data/rules/always_specify_types.dart
@@ -4,15 +4,40 @@
 
 // test w/ `dart test -N always_specify_types`
 
+// ignore_for_file: unused_local_variable
+
 import 'package:meta/meta.dart';
 
+/// https://github.com/dart-lang/linter/issues/3275
+typedef Foo1 = Map<String, Object>;
+final Foo1 foo = Foo1();
+
+/// Constructor tear-offs
+void constructorTearOffs() {
+  List<List>.filled; // LINT
+  List<E> Function<E>(int, E) filledList = List.filled; // OK - generic function
+  filledList<int>(3, 3); // OK
+  filledList(3, 3); // OK - generic function invocations are uncovered -- see: #2914
+}
+
+typedef MapList = List<StringMap>; //LINT
+typedef JsonMap = Map<String, dynamic>; //OK
+typedef StringList = List<JsonMap>; //OK
+typedef RawList = List; //LINT
+typedef StringMap<V> = Map<String, V>; //OK
+
+StringMap<String> sm = StringMap<String>(); //OK
+StringMap? rm1; //LINT
+StringMap<String> rm2 = StringMap(); //LINT
+StringMap rm3 = StringMap<String>(); //LINT
+
 Map<String, String> map = {}; //LINT
 List<String> strings = []; //LINT
 Set<String> set = {}; //LINT
 
-List list; // LINT
-List<List> lists; //LINT
-List<int> ints; //OK
+List? list; //LINT
+List<List>? lists; //LINT
+List<int> ints = <int>[1]; //OK
 
 final x = 1; //LINT [1:5]
 final int xx = 3;
@@ -43,6 +68,7 @@
     print(i);
   }
   List<String> ls = <String>[];
+  // ignore: avoid_function_literals_in_foreach_calls
   ls.forEach((s) => print(s)); //LINT [15:1]
   for (var l in ls) { //LINT [8:3]
     print(l);
@@ -57,13 +83,14 @@
     print(e);
   }
 
+  // ignore: non_constant_identifier_names
   var __; // LINT
 
   listen((_) { // OK!
     // ...
   });
 
-  P p = new P(); //OK (optionalTypeArgs)
+  P p = P(); //OK (optionalTypeArgs)
 }
 
 P doSomething(P p) //OK (optionalTypeArgs)
@@ -71,7 +98,7 @@
   return p;
 }
 
-listen(void onData(Object event)) {}
+listen(void Function(Object event) onData) {}
 
 var z; //LINT
 
@@ -85,8 +112,8 @@
 }
 
 void m() {
-  if ('' is Map) //OK {
+  if ('' is Map) //OK
   {
-     print("won't happen");
+    print("won't happen");
   }
 }
diff --git a/test_data/rules/avoid_as.dart b/test_data/rules/avoid_as.dart
index af4c3be..70facf1 100644
--- a/test_data/rules/avoid_as.dart
+++ b/test_data/rules/avoid_as.dart
@@ -8,7 +8,7 @@
   var pm;
   try {
     (pm as Person).firstName = 'Seth'; //LINT [12:6]
-  } on CastError {} // ignore: avoid_catching_errors, empty_catches
+  } on Error {} // ignore: avoid_catching_errors, empty_catches
 
   Person person = pm;
   person.firstName = 'Seth';
diff --git a/test_data/rules/avoid_bool_literals_in_conditional_expressions.dart b/test_data/rules/avoid_bool_literals_in_conditional_expressions.dart
index 57bc007..f03862f 100644
--- a/test_data/rules/avoid_bool_literals_in_conditional_expressions.dart
+++ b/test_data/rules/avoid_bool_literals_in_conditional_expressions.dart
@@ -4,7 +4,7 @@
 
 // test w/ `dart test -N avoid_bool_literals_in_conditional_expressions`
 
-bool a, b, c;
+late bool a, b, c;
 
 var d = a ? true : b; // LINT
 var e = a ? false : b; // LINT
diff --git a/test_data/rules/avoid_catching_errors.dart b/test_data/rules/avoid_catching_errors.dart
index 4bd7f5b..6d6ab87 100644
--- a/test_data/rules/avoid_catching_errors.dart
+++ b/test_data/rules/avoid_catching_errors.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N avoid_catching_errors`
 
+// ignore_for_file: unused_catch_clause
+
 void bad() {
   try {} on A catch (e) { // LINT
     // ignore
@@ -28,7 +30,7 @@
 
 class B implements Error {
   @override
-  StackTrace get stackTrace => null;
+  StackTrace? get stackTrace => null;
 }
 
 class C extends D {}
diff --git a/test_data/rules/experiments/nnbd/rules/avoid_dynamic_calls.dart b/test_data/rules/avoid_dynamic_calls.dart
similarity index 100%
rename from test_data/rules/experiments/nnbd/rules/avoid_dynamic_calls.dart
rename to test_data/rules/avoid_dynamic_calls.dart
diff --git a/test_data/rules/avoid_equals_and_hash_code_on_mutable_classes.dart b/test_data/rules/avoid_equals_and_hash_code_on_mutable_classes.dart
index c9dab21..6d282ee 100644
--- a/test_data/rules/avoid_equals_and_hash_code_on_mutable_classes.dart
+++ b/test_data/rules/avoid_equals_and_hash_code_on_mutable_classes.dart
@@ -13,7 +13,7 @@
   @override
   operator ==(other) => other is A && other.key == key; // OK
   @override
-  int hashCode() => key.hashCode; // OK
+  int get hashCode => key.hashCode; // OK
 }
 
 class B {
@@ -22,7 +22,7 @@
   @override
   operator ==(other) => other is B && other.key == key; // LINT
   @override
-  int hashCode() => key.hashCode; // LINT
+  int get hashCode => key.hashCode; // LINT
 }
 
 @immutable
diff --git a/test_data/rules/avoid_final_parameters.dart b/test_data/rules/avoid_final_parameters.dart
new file mode 100644
index 0000000..a0db33b
--- /dev/null
+++ b/test_data/rules/avoid_final_parameters.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N avoid_final_parameters`
+
+void badRequiredPositional(final String label) { // LINT
+  print(label);
+}
+
+void goodRequiredPositional(String label) { // OK
+  print(label);
+}
+
+void badOptionalPosition([final String? label]) { // LINT
+  print(label);
+}
+
+void goodOptionalPosition([String? label]) { // OK
+  print(label);
+}
+
+void badRequiredNamed({required final String label}) { // LINT
+  print(label);
+}
+
+void goodRequiredNamed({required String label}) { // OK
+  print(label);
+}
+
+void badOptionalNamed({final String? label}) { // LINT
+  print(label);
+}
+
+void goodOptionalNamed({String? label}) { // OK
+  print(label);
+}
+
+void badExpression(final int value) => print(value); // LINT
+
+void goodExpression(int value) => print(value); // OK
+
+bool? _testingVariable;
+
+void set badSet(final bool setting) => _testingVariable = setting; // LINT
+
+void set goodSet(bool setting) => _testingVariable = setting; // OK
+
+var badClosure = (final Object random) { // LINT
+  print(random);
+};
+
+var goodClosure = (Object random) { // OK
+  print(random);
+};
+
+var _testingList = [1, 7, 15, 20];
+
+void useBadClosureArgument() {
+  _testingList.forEach((final element) => print(element + 4)); // LINT
+}
+
+void useGoodClosureArgument() {
+  _testingList.forEach((element) => print(element + 4)); // OK
+}
+
+void useGoodTypedClosureArgument() {
+  _testingList.forEach((int element) => print(element + 4)); // OK
+}
+
+void badMixedLast(final String bad, String good) { // LINT
+  print(bad);
+  print(good);
+}
+
+void badMixedFirst(String goodFirst, final String badSecond) { // LINT
+  print(goodFirst);
+  print(badSecond);
+}
+
+// LINT [+1]
+void badMixedMiddle(final String badFirst, String goodSecond, final String badThird) { // LINT
+  print(badFirst);
+  print(goodSecond);
+  print(badThird);
+}
+
+void goodMultiple(String bad, String good) { // OK
+  print(bad);
+  print(good);
+}
+
+class C {
+  String value = '';
+  int _contents = 0;
+
+  C(final String content) { // LINT
+    _contents = content.length;
+  }
+
+  C.bad(final int contents) : _contents = contents; // LINT
+
+  C.good(int contents) : _contents = contents; // OK
+
+  C.badValue(final String value) : this.value = value; // LINT
+
+  C.goodValue(this.value); // OK
+
+  factory C.goodFactory(String value) { // OK
+    return C(value);
+  }
+
+  factory C.badFactory(final String value) { // LINT
+    return C(value);
+  }
+
+  void set badContents(final int contents) => _contents = contents; // LINT
+  void set goodContents(int contents) => _contents = contents; // OK
+
+  int get contentValue => _contents + 4; // OK
+
+  void badMethod(final String bad) { // LINT
+    print(bad);
+  }
+
+  void goodMethod(String good) { // OK
+    print(good);
+  }
+
+  @override
+  C operator +(final C other) { // LINT
+    return C.good(contentValue + other.contentValue);
+  }
+
+  @override
+  C operator -(C other) { // OK
+    return C.good(contentValue + other.contentValue);
+  }
+}
diff --git a/test_data/rules/avoid_function_literals_in_foreach_calls.dart b/test_data/rules/avoid_function_literals_in_foreach_calls.dart
index 6a4e696..080426e 100644
--- a/test_data/rules/avoid_function_literals_in_foreach_calls.dart
+++ b/test_data/rules/avoid_function_literals_in_foreach_calls.dart
@@ -4,14 +4,12 @@
 
 // test w/ `dart test -N avoid_function_literals_in_foreach_calls`
 
-void f(dynamic iter) => iter?.forEach(...); // OK
-
 class Person {
-  Iterable<Person> children;
+  Iterable<Person> children = [];
 }
 
 void main() {
-  Iterable<String> people = [];
+  Iterable<String?> people = [];
 
   for (var person in people) {
     // OK
@@ -28,17 +26,17 @@
 
   people
       .where((person) => person != null)
-      .map((person) => person.toUpperCase())
+      .map((person) => person!.toUpperCase())
       .forEach((person) => print('$person!')); // OK
 
   people
       .where((person) => person != null)
-      .map((person) => person.toUpperCase())
+      .map((person) => person!.toUpperCase())
       .forEach(print); // OK
 
   Person()
       .children
-      .firstWhere((person) => person != null)
+      .firstWhere((person) => person.children.isNotEmpty)
       .children
       .forEach((person) => print('$person!')); // OK
 
diff --git a/test_data/rules/avoid_multiple_declarations_per_line.dart b/test_data/rules/avoid_multiple_declarations_per_line.dart
index 7ba3fa0..d35c9a4 100644
--- a/test_data/rules/avoid_multiple_declarations_per_line.dart
+++ b/test_data/rules/avoid_multiple_declarations_per_line.dart
@@ -51,3 +51,11 @@
   static String? bar;
   static String? baz;
 }
+
+// https://github.com/dart-lang/linter/issues/2543
+okInForLoop() {
+  for (var i = 0, j = 0; i < 2 && j < 2; ++i, ++j) // OK
+  {
+    //
+  }
+}
diff --git a/test_data/rules/avoid_null_checks_in_equality_operators.dart b/test_data/rules/avoid_null_checks_in_equality_operators.dart
index 079ef70..019a3b7 100644
--- a/test_data/rules/avoid_null_checks_in_equality_operators.dart
+++ b/test_data/rules/avoid_null_checks_in_equality_operators.dart
@@ -4,13 +4,36 @@
 
 // test w/ `dart test -N avoid_null_checks_in_equality_operators`
 
+
+class P {
+  final String name = '';
+
+  @override
+  operator ==(other) {
+    return other != null && //OK: https://github.com/dart-lang/linter/issues/2864
+        other is P &&
+        name == other.name;
+  }
+}
+
+class P2 {
+  final String name = '';
+
+  @override
+  operator ==(Object other) {
+    return other != null && //OK: https://github.com/dart-lang/linter/issues/2864
+        other is P2 &&
+        name == other.name;
+  }
+}
+
 class BadPerson1 {
   final String name = 'I am a bad person';
 
   get age => 42;
 
   @override
-  operator ==(other) =>
+  operator ==(Object? other) =>
           other != null && // LINT
           other is BadPerson1 &&
           name == other.name;
@@ -20,7 +43,7 @@
   final String name = 'I am a bad person';
 
   @override
-  operator ==(other) =>
+  operator ==(Object? other) =>
           !(other == null) && // LINT
           other is BadPerson2 &&
           name == other.name;
@@ -30,7 +53,7 @@
   final String name = 'I am a bad person';
 
   @override
-  operator ==(other) =>
+  operator ==(Object? other) =>
           other is BadPerson3
               &&
           name == other?.name; // LINT
@@ -42,7 +65,7 @@
   String getName() => name;
 
   @override
-  operator ==(other) =>
+  operator ==(Object? other) =>
       other is BadPerson4
           &&
           name == other?.getName(); // LINT
@@ -54,7 +77,7 @@
   BadPerson5(this.name);
 
   @override
-  operator ==(other) {
+  operator ==(Object? other) {
     if (other is BadPerson5){
       final toCompare = other ?? new BadPerson5(""); // LINT
       return toCompare.name == name;
diff --git a/test_data/rules/avoid_positional_boolean_parameters.dart b/test_data/rules/avoid_positional_boolean_parameters.dart
index 99fcf08..2eb058c 100644
--- a/test_data/rules/avoid_positional_boolean_parameters.dart
+++ b/test_data/rules/avoid_positional_boolean_parameters.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N avoid_positional_boolean_parameters`
 
+// ignore_for_file: unused_element
+
 class Library {
   void checkRegularBook(String name) {
     _checkBook(name, false);
@@ -16,7 +18,7 @@
   void _checkBook(String name, bool isPremium) {} // OK because it is private.
 }
 
-void good({bool a}) { // OK
+void good({bool a = false}) { // OK
   _good(a);
 }
 
@@ -25,20 +27,20 @@
 void _good(bool a) {} // OK because it is private.
 
 class A {
-  void good({bool a}) {} // OK
+  void good({bool a = false}) {} // OK
 
   void bad(bool a) {} // LINT
 }
 
 class B {
-  static void good({bool a}) {} // OK
+  static void good({bool a = false}) {} // OK
 
   static void bad(bool a) {} // LINT
 }
 
 class C {
-  bool value;
-  C.good({bool value}) { // OK
+  late bool value;
+  C.good({bool value = false}) { // OK
     this.value = value;
   }
 
@@ -58,13 +60,13 @@
 
 class D {
   bool value;
-  D.good({this.value}); // OK
+  D.good({this.value = false}); // OK
 
   D.bad(this.value); // LINT
 }
 
 class E {
-  void bad([bool value]) {} // LINT
+  void bad([bool value = false]) {} // LINT
 
   void good({bool value: true}) {} // OK
 }
@@ -74,17 +76,17 @@
 }
 
 class G {
-  G._internal([bool value]); // OK because is private
+  G._internal([bool value = false]); // OK because is private
 }
 
 class H extends E {
   @override
-  void bad([bool value]) {} // OK because it has inherited method.
+  void bad([bool value = false]) {} // OK because it has inherited method.
 }
 
 abstract class I implements E {
   @override
-  void bad([bool value]) {} // OK because it has inherited method.
+  void bad([bool value = false]) {} // OK because it has inherited method.
 }
 
 void closureAsArgument() {
@@ -93,9 +95,9 @@
 }
 
 extension Ext on E {
-  void badBad([bool value]) {} // LINT
+  void badBad([bool value = false]) {} // LINT
 }
 
 extension on E {
-  void badBadBad([bool value]) {} // LINT
+  void badBadBad([bool value = false]) {} // LINT
 }
diff --git a/test_data/rules/avoid_print.dart b/test_data/rules/avoid_print.dart
index 9dd1058..492b0bb 100644
--- a/test_data/rules/avoid_print.dart
+++ b/test_data/rules/avoid_print.dart
@@ -4,10 +4,16 @@
 
 // test w/ `dart test -N avoid_print`
 
+import 'package:flutter/foundation.dart';
+
 void main() {
   print('ha'); // LINT
   [1,2,3].forEach(print); // LINT
   Future.value('hello').then(print); // LINT
+  if (kDebugMode) print(''); // OK
+  if (kDebugMode) {
+    print(''); // OK
+  }
 }
 
 
diff --git a/test_data/rules/avoid_private_typedef_functions.dart b/test_data/rules/avoid_private_typedef_functions.dart
index efa3642..1d51688 100644
--- a/test_data/rules/avoid_private_typedef_functions.dart
+++ b/test_data/rules/avoid_private_typedef_functions.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N avoid_private_typedef_functions`
 
+// ignore_for_file: unused_element
+
 typedef void _F1(); // LINT
 typedef void F1(); // OK
 
@@ -18,12 +20,12 @@
 m4b(_F4 f) => null;
 
 typedef void _F5(); // OK
-_F5 v5a;
-_F5 v5b;
+_F5 v5a = () {};
+_F5 v5b = () {};
 
 typedef void _F6(); // OK
-_F6 f6a() => null;
-_F6 f6b() => null;
+_F6 f6a() => () {};
+_F6 f6b() => () {};
 
 typedef _F7 = void Function(); // OK
 m7(_F7 f) => null;
diff --git a/test_data/rules/avoid_redundant_argument_values.dart b/test_data/rules/avoid_redundant_argument_values.dart
index 371a8dc..4c01518 100644
--- a/test_data/rules/avoid_redundant_argument_values.dart
+++ b/test_data/rules/avoid_redundant_argument_values.dart
@@ -7,24 +7,42 @@
 import 'package:meta/meta.dart';
 
 class A {
-  A({bool valWithDefault = true, bool val});
-  void f({bool valWithDefault = true, bool val}) {}
-  void g({int valWithDefault = 1, bool val}) {}
-  void h({String valWithDefault = 'default', bool val}) {}
+  A({bool valWithDefault = true, bool? val});
+  void f({bool valWithDefault = true, bool? val}) {}
+  void g({int valWithDefault = 1, bool? val}) {}
+  void h({String valWithDefault = 'default', bool? val}) {}
+}
+
+enum TestEnum {
+  a(test: false); // LINT
+
+  const TestEnum({this.test = false});
+
+  final bool test;
+}
+
+f(void g([bool? b])) {
+  // Function Expression Invocation.
+  g(null); // LINT
 }
 
 bool q() => true;
 
-void ff({bool valWithDefault = true, bool val}) {}
-void g({@required bool valWithDefault = true, bool val}) {}
+void ff({bool valWithDefault = true, bool? val}) {}
+void g({@required bool valWithDefault = true, bool? val}) {}
 
 void gg(int x, [int y = 0]) {}
 void ggg([int a = 1, int b = 2]) {}
-void gggg([int a = 0, int b]) {}
+void gggg([int a = 0, int? b]) {}
 
-void h([int a, int b = 1]) {}
+void h([int? a, int? b = 1]) {}
 
 void main() {
+
+  // Tear-off
+  var aCons = A.new;
+  aCons(valWithDefault: true); //LINT
+
   A(valWithDefault: true); //LINT
   A().f(valWithDefault: true); //LINT
   A().g(valWithDefault: 1); //LINT
@@ -44,7 +62,7 @@
   ff(val: false, valWithDefault: v); //OK
   ff(val: false, valWithDefault: q()); //OK
 
-  void fff({bool valWithDefault = true, bool val}) {}
+  void fff({bool valWithDefault = true, bool? val}) {}
 
   fff(valWithDefault: true); //LINT
   fff(val: false); //OK
diff --git a/test_data/rules/avoid_relative_lib_imports.dart b/test_data/rules/avoid_relative_lib_imports.dart
index dda3f46..2665b97 100644
--- a/test_data/rules/avoid_relative_lib_imports.dart
+++ b/test_data/rules/avoid_relative_lib_imports.dart
@@ -4,6 +4,6 @@
 
 // test w/ `dart test -N avoid_relative_lib_imports`
 
-import '../_data/avoid_relative_lib_imports/lib/dummy.dart'; //LINT
+import '../integration/avoid_relative_lib_imports/lib/dummy.dart'; //LINT
 import 'avoid_catching_errors.dart'; //OK
 
diff --git a/test_data/rules/avoid_returning_this.dart b/test_data/rules/avoid_returning_this.dart
index fab9d93..258d770 100644
--- a/test_data/rules/avoid_returning_this.dart
+++ b/test_data/rules/avoid_returning_this.dart
@@ -4,11 +4,13 @@
 
 // test w/ `dart test -N avoid_returning_this`
 
+// ignore_for_file: unused_element
+
 class A {
-  int x;
-  A badAddOne() { // LINT
+  int x = 0;
+  A badAddOne() {
     x++;
-    return this;
+    return this; // LINT
   }
 
   Object goodAddOne1() { // OK it is ok because it does not return an A type.
@@ -20,13 +22,13 @@
     x++;
     return this.x;
   }
-  A getThing() { // LINT
-    return this;
+  A getThing() {
+    return this; // LINT
   }
 
   B doSomething() { // OK it is ok because it does not return an A type.
     x++;
-    return this;
+    return B();
   }
 
   A operator +(int n) { // OK it is ok because it is an operator.
@@ -48,31 +50,31 @@
     return this;
   }
 
-  B badAddOne2() { // LINT
+  B badAddOne2() {
     x++;
-    return this;
+    return this; // LINT
   }
 
-  B badOne3() { // LINT
+  B badOne3() {
     int a() {
       return 1;
     }
     x = a();
-    return this;
+    return this; // LINT
   }
 
-  B badOne4() { // LINT
+  B badOne4() {
     int a() => 1;
     x = a();
-    return this;
+    return this; // LINT
   }
 
-  B badOne5() { // LINT
+  B badOne5() {
     final a = () {
       return 1;
     };
     x = a();
-    return this;
+    return this; // LINT
   }
 }
 
@@ -92,8 +94,8 @@
     return this;
   }
 
-  D _m() { // LINT
-    return this;
+  D _m() {
+    return this; // LINT
   }
 }
 class E implements C<E> {
diff --git a/test_data/rules/avoid_shadowing_type_parameters.dart b/test_data/rules/avoid_shadowing_type_parameters.dart
index 0d31ffb..94d809f 100644
--- a/test_data/rules/avoid_shadowing_type_parameters.dart
+++ b/test_data/rules/avoid_shadowing_type_parameters.dart
@@ -75,4 +75,3 @@
 typedef Fn4<T> = void Function(T); // OK
 typedef Fn5 = void Function<T>(T); // OK
 
-typedef Predicate = bool <E>(E element); // OK
diff --git a/test_data/rules/avoid_type_to_string.dart b/test_data/rules/avoid_type_to_string.dart
index feb753f..79864fb 100644
--- a/test_data/rules/avoid_type_to_string.dart
+++ b/test_data/rules/avoid_type_to_string.dart
@@ -7,14 +7,14 @@
 // SHARED
 
 class A {
-  String toString() {}
+  String toString() => '';
 }
 
-String takesFunction(Function f) {}
+String takesFunction(Function f)  => '';
 
 class TypeChildWithOverride extends Type {
   @override
-  String toString() {}
+  String toString()  => '';
 }
 
 class TypeGrandChildWithOverride extends TypeChildWithOverride {}
@@ -24,7 +24,7 @@
 class TypeGrandChildNoOverride extends TypeChildNoOverride {}
 
 mixin ToStringMixin {
-  String toString() {}
+  String toString()  => '';
 }
 
 // BAD
@@ -38,7 +38,7 @@
 }
 
 class BadWithType extends Type {
-  Function passedFunction;
+  Function passedFunction = (){};
 
   BadWithType(Function func) : this.withFunc(func);
   BadWithType.withoutFunc() {}
@@ -114,7 +114,7 @@
   GoodWithType.withOther(Good good) : this.withFunc(good.toString); // OK
 
   void good() {
-    String toString() => null;
+    String toString() => '';
     toString(); // OK
   }
 }
@@ -145,6 +145,7 @@
 }
 
 extension on int Function(int) {
+  // ignore: unused_element
   void extendedGood() {
     toString(); // OK
     this.toString(); // OK
diff --git a/test_data/rules/avoid_types_as_parameter_names.dart b/test_data/rules/avoid_types_as_parameter_names.dart
index fc46a4b..28f5161 100644
--- a/test_data/rules/avoid_types_as_parameter_names.dart
+++ b/test_data/rules/avoid_types_as_parameter_names.dart
@@ -41,8 +41,6 @@
 m9(f7) => null; // LINT
 m10(m1) => null; // OK
 
-final void Function(Object, [StackTrace]) onError = null; // OK
-
 /// Naming the field `num` is significant - should be the name of a class.
 class FieldFormalParameter {
   final int num;
diff --git a/test_data/rules/avoid_types_on_closure_parameters.dart b/test_data/rules/avoid_types_on_closure_parameters.dart
index 766061c..e43cb51 100644
--- a/test_data/rules/avoid_types_on_closure_parameters.dart
+++ b/test_data/rules/avoid_types_on_closure_parameters.dart
@@ -5,22 +5,22 @@
 // test w/ `dart test -N avoid_types_on_closure_parameters`
 
 class Person {
-  String name;
+  String name = '';
 }
 
-List<Person> people;
+List<Person> people = [];
 
 var goodName1 = people.map((person) => person.name); // OK
 var badName1 = people.map((Person person) => person.name); // LINT
 
 var goodName2 = ({person}) => person.name; // OK
-var badName2 = ({Person person}) => person.name; // LINT
+var badName2 = ({required Person person}) => person.name; // LINT
 
-var goodName3 = ({person : ""}) => person; // OK
-var badName3 = ([String person = ""]) => person; // LINT
+var goodName3 = ({person : ''}) => person; // OK
+var badName3 = ([String person = '']) => person; // LINT
 
 var goodName4 = ([person]) => person.name; // OK
-var badName4 = ([Person person]) => person.name; // LINT
+var badName4 = ([Person? person]) => person?.name; // LINT
 
 var goodName5 = (dynamic person) => person.name; // OK
 
diff --git a/test_data/rules/avoid_unused_constructor_parameters.dart b/test_data/rules/avoid_unused_constructor_parameters.dart
index c3afe06..5c573e6 100644
--- a/test_data/rules/avoid_unused_constructor_parameters.dart
+++ b/test_data/rules/avoid_unused_constructor_parameters.dart
@@ -9,17 +9,17 @@
 }
 
 class B {
-  int a;
-  int b;
+  int a = 0;
+  int b = 0;
 
-  B(this.a, [this.b]); // OK because field formal parameters are being used
+  B(this.a, [this.b = 0]); // OK because field formal parameters are being used
 }
 
 class C {
   int a;
   int b;
 
-  C({this.a, this.b}); // OK because field formal parameters are being used
+  C({this.a = 0, this.b = 0}); // OK because field formal parameters are being used
 }
 
 class D {
@@ -28,7 +28,7 @@
 }
 
 class E {
-  int c;
+  int c = 0;
 
   E(int a, {int b = 10}) { // OK because all parameters are used
     c = a + b;
@@ -36,7 +36,7 @@
 }
 
 class F {
-  int n;
+  int n = 0;
 
   F(int a, [int b = 10, // LINT
     int c = 42]) { // LINT
@@ -45,10 +45,10 @@
 }
 
 class G {
-  int c;
-  int d;
+  int c = 0;
+  int d = 0;
 
-  G(int a, {int b, this.c}) { // OK because all non-field-formal parameters are used
+  G(int a, {int b = 0, this.c = 0}) { // OK because all non-field-formal parameters are used
     d = a + b;
   }
 }
@@ -64,7 +64,7 @@
 }
 
 class J extends H {
-  int d;
+  int d = 0;
 
   J(int a, int b, int c) : super(a, b) { // OK because all parameters are used
     d = a * b * c;
@@ -72,10 +72,10 @@
 }
 
 class K {
-  int a;
-  int b;
+  int a = 0;
+  int b = 0;
 
-  K(this.a, {this.b, int c}); // LINT
+  K(this.a, {this.b = 0, int c = 0}); // LINT
 }
 
 class L {
diff --git a/test_data/rules/avoid_void_async.dart b/test_data/rules/avoid_void_async.dart
index d16d77c..1b85b5e 100644
--- a/test_data/rules/avoid_void_async.dart
+++ b/test_data/rules/avoid_void_async.dart
@@ -19,8 +19,6 @@
 void e() => null; // OK
 Future<void> f() async => null; // OK
 
-void g() async* {} // LINT
-void h() sync* {} // LINT
 Stream<void> i() async* {} // OK
 Iterable<void> j() sync* {} // OK
 
@@ -46,8 +44,6 @@
   void e() => null; // OK
   Future<void> f() async => null; // OK
 
-  void g() async* {} // LINT
-  void h() sync* {} // LINT
   Stream<void> i() async* {} // OK
   Iterable<void> j() sync* {} // OK
 
diff --git a/test_data/rules/await_only_futures.dart b/test_data/rules/await_only_futures.dart
index dacf4c7..727719b 100644
--- a/test_data/rules/await_only_futures.dart
+++ b/test_data/rules/await_only_futures.dart
@@ -25,13 +25,13 @@
   }
 
   @override
-  Future<T> catchError(Function onError, {bool test(Object error)}) {
+  Future<T> catchError(Function onError, {bool test(Object error)?}) {
     throw new Exception('Not supported.');
   }
 
 
   @override
-  Future<T> timeout(Duration timeLimit, {onTimeout()}) {
+  Future<T> timeout(Duration timeLimit, {onTimeout()?}) {
     throw new Exception('Not supported.');
   }
 
@@ -41,7 +41,7 @@
   }
 
   @override
-  Future<R> then<R>(FutureOr<R> Function(T value) onValue, {Function onError}) {
+  Future<R> then<R>(FutureOr<R> Function(T value) onValue, {Function? onError}) {
     throw new Exception('Not supported.');
   }
 }
diff --git a/test_data/rules/camel_case_types.dart b/test_data/rules/camel_case_types.dart
index 1318109..ae8d4b6 100644
--- a/test_data/rules/camel_case_types.dart
+++ b/test_data/rules/camel_case_types.dart
@@ -16,6 +16,8 @@
 
 typedef bool predicate(); //LINT [14:9]
 
+typedef bool Predicate(); //OK
+
 class fooBar // LINT
 {}
 
@@ -27,3 +29,16 @@
 
 class $FooBar //OK
 {}
+
+typedef foo = Foo; //LINT
+
+typedef F = Foo; //OK
+
+typedef f = void Function(); //LINT
+
+mixin M {}
+class c = Object with M; //LINT
+
+enum foooBar { a } //LINT
+
+enum FoooBar { a } //OK
diff --git a/test_data/rules/cascade_invocations.dart b/test_data/rules/cascade_invocations.dart
index 2e92b83..8212f88 100644
--- a/test_data/rules/cascade_invocations.dart
+++ b/test_data/rules/cascade_invocations.dart
@@ -57,7 +57,7 @@
 }
 
 void noCascadeWithGetter() {
-  final foo = new Foo();
+  final foo = Foo();
   foo.baz(); // LINT
   foo.bar; // LINT
   foo.foo(); // LINT
@@ -111,25 +111,25 @@
 
 // https://github.com/dart-lang/linter/issues/339
 class Identifier339 {
-  String value;
-  String system;
+  String value = '';
+  String system = '';
 }
 
 class Resource339 {
-  String id;
+  String id = '';
   dynamic identifier;
 }
 
-class Practitioner339 extends Resource339{
-  int foo;
+class Practitioner339 extends Resource339 {
+  int foo = 0;
 
-  String build() => null;
+  String build() => '';
 
   void bar(dynamic) {}
 }
 
 class Entry339 {
-  Resource339 resource;
+  Resource339 resource = Resource339();
 }
 
 void main339() {
@@ -163,8 +163,8 @@
 }
 
 class Bug661 {
-  Bug661 parent;
-  Practitioner339 practitioner;
+  late Bug661 parent;
+  late Practitioner339 practitioner;
 
   void bar() {
     practitioner.bar(1);
@@ -174,11 +174,11 @@
 }
 
 class Bug701 {
-  int foo;
-  int bar;
+  int foo = 0;
+  int bar = 0;
 }
 
-void function701({Bug701 something}) {
+void function701({Bug701? something}) {
   something ??= new Bug701();
   something.foo = 1; // OK
   something.bar = 2; // LINT
@@ -196,11 +196,13 @@
 }
 
 class A {
-  int f;
-  int g;
+  late int f;
+  late int g;
 
   int get p => 7;
   int get q => 6;
+  int m() => 5;
+  void set s(int value) {}
 }
 
 class B {
@@ -227,6 +229,21 @@
     ..p
     ..q;
   b2.a.p; // OK
+
+  b1.a
+    ..p
+    ..q;
+  b2.a..p; // OK
+
+  b1.a
+    ..p
+    ..m();
+  b2.a.m(); // OK
+
+  b1.a
+    ..p
+    ..s = 1;
+  b2.a.s = 1; // OK
 }
 
 void bug1317() async {
diff --git a/test_data/rules/experiments/nnbd/rules/cast_nullable_to_non_nullable.dart b/test_data/rules/cast_nullable_to_non_nullable.dart
similarity index 100%
rename from test_data/rules/experiments/nnbd/rules/cast_nullable_to_non_nullable.dart
rename to test_data/rules/cast_nullable_to_non_nullable.dart
diff --git a/test_data/rules/combinators_ordering.dart b/test_data/rules/combinators_ordering.dart
new file mode 100644
index 0000000..afd6ee3
--- /dev/null
+++ b/test_data/rules/combinators_ordering.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N combinators_ordering`
+
+import 'dart:math' as m1 show max, min; // OK
+import 'dart:math' as m2 show min, max; // LINT
+
+export 'dart:math' show max, min; // OK
+export 'dart:math' show min, max; // LINT
+
+import 'dart:math' as m3 hide max, min; // OK
+import 'dart:math' as m4 hide min, max; // LINT
+
+export 'dart:math' hide max, min; // OK
+export 'dart:math' hide min, max; // LINT
diff --git a/test_data/rules/comment_references.dart b/test_data/rules/comment_references.dart
index e5553c3..5896c3a 100644
--- a/test_data/rules/comment_references.dart
+++ b/test_data/rules/comment_references.dart
@@ -16,7 +16,7 @@
 /// [String] is OK.
 class A {
   /// But [zap] is not. #LINT
-  int z;
+  int z = 0;
 
   /// Reads [x] and assigns to [z]. #OK
   void read(int x) {}
@@ -28,5 +28,24 @@
 /// [
 /// ^--- Should not crash (#819).
 class B {
+}
 
+/// A link to [Sha256][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+class C {
+}
+
+/// A link to [Sha256](http://tools.ietf.org/html/rfc6234) hash function.
+class D {
+}
+
+/// A link to [Sha256](http://tools.ietf.org/html/rfc6234 "Some") hash function.
+class E {
+}
+
+/// A link to [rfc][] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+class F {
 }
diff --git a/test_data/rules/control_flow_in_finally.dart b/test_data/rules/control_flow_in_finally.dart
index 80ad629..8a92735 100644
--- a/test_data/rules/control_flow_in_finally.dart
+++ b/test_data/rules/control_flow_in_finally.dart
@@ -6,7 +6,7 @@
 
 class Ok {
   double compliantMethod() {
-    var i = 5;
+    var i = 5.0;
     try {
       i = 1 / 0;
     } catch (e) {
@@ -31,7 +31,7 @@
 }
 
 class GoodReturn {
-  double compliantMethod() {
+  double? compliantMethod() {
     try {
       return 1 / 0;
     } catch (e) {
diff --git a/test_data/rules/diagnostic_describe_all_properties.dart b/test_data/rules/diagnostic_describe_all_properties.dart
index c9d8d6c..7adc568 100644
--- a/test_data/rules/diagnostic_describe_all_properties.dart
+++ b/test_data/rules/diagnostic_describe_all_properties.dart
@@ -7,29 +7,29 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
-class MyWidget with Diagnosticable {
-  Widget p0; //Skipped
-  List<Widget> p00; //Skipped
-  Widget get p000 => null; //Skipped
-  String p1; //OK
-  String p2; //LINT
+class MyWidget extends Diagnosticable {
+  Widget? p0; //Skipped
+  List<Widget> p00 = []; //Skipped
+  Widget? get p000 => null; //Skipped
+  String p1 = ''; //OK
+  String p2 = ''; //LINT
   String get p3 => ''; //LINT
-  String _p3; //OK
-  String debugFoo; //OK
-  String foo; //OK (covered by debugFoo)
-  String debugBar; //OK (covered by bar)
-  String bar; //OK
-  static String p4; //OK
-  String p5; //OK (in debugDescribeChildren)
+  String _p3 = ''; //OK
+  String debugFoo = ''; //OK
+  String foo = ''; //OK (covered by debugFoo)
+  String debugBar = ''; //OK (covered by bar)
+  String bar = ''; //OK
+  static String p4 = ''; //OK
+  String p5 = ''; //OK (in debugDescribeChildren)
 
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     properties
-        .add(StringProperty('property', p1, defaultValue: null, quoted: false));
+        .add(StringProperty('property', p1, defaultValue: '', quoted: false));
     properties.add(StringProperty('debugFoo', debugFoo,
-        defaultValue: null, quoted: false));
+        defaultValue: '', quoted: false));
     properties
-        .add(StringProperty('bar', bar, defaultValue: null, quoted: false));
+        .add(StringProperty('bar', bar, defaultValue: '', quoted: false));
   }
 
   @override
@@ -37,10 +37,10 @@
     // In real source this should be used to create a diagnostics node,
     // but for us a reference suffices.
     print(p5);
-    return null;
+    return [];
   }
 }
 
-class MyWidget2 with Diagnosticable {
-  bool property; //LINT
+class MyWidget2 extends Diagnosticable {
+  bool property = false; //LINT
 }
diff --git a/test_data/rules/exhaustive_cases.dart b/test_data/rules/exhaustive_cases.dart
index d545aa0..7115f46 100644
--- a/test_data/rules/exhaustive_cases.dart
+++ b/test_data/rules/exhaustive_cases.dart
@@ -16,25 +16,40 @@
 
 void e(E e) {
   // Missing case.
-  switch(e) { // LINT
-    case E.e :
+  switch (e) { // LINT
+    case E.e:
       print('e');
       break;
-    case E.f :
+    case E.f:
       print('e');
   }
 }
 
 void ok(E e) {
   // All cases covered.
-  switch(e) { // OK
-    case E.e :
+  switch (e) { // OK
+    case E.e:
       print('e');
       break;
-    case E.f :
+    case E.f:
       print('e');
       break;
-    case E.g :
+    case E.g:
+      print('e');
+      break;
+  }
+}
+
+void okParenthesized(E e) {
+  // All cases covered.
+  switch (e) { // OK
+    case (E.e):
+      print('e');
+      break;
+    case ((E.f)):
+      print('e');
+      break;
+    case (E.g):
       print('e');
       break;
   }
@@ -42,17 +57,17 @@
 
 void okDefault(E e) {
   // Missing cases w/ default is OK.
-  switch(e) { // OK
-    case E.e :
+  switch (e) { // OK
+    case E.e:
       print('e');
       break;
-    default :
+    default:
       print('default');
       break;
   }
 }
 
-// Not Enum-like
+// Not Enum-like.
 class Subclassed {
   const Subclassed._();
 
@@ -66,13 +81,13 @@
 }
 
 void s(Subclassed e) {
-  switch(e) { // OK
-    case Subclassed.e :
+  switch (e) { // OK
+    case Subclassed.e:
       print('e');
   }
 }
 
-// Not Enum-like
+// Not Enum-like.
 class TooFew {
   const TooFew._();
 
@@ -80,13 +95,13 @@
 }
 
 void t(TooFew e) {
-  switch(e) { // OK
-    case TooFew.e :
+  switch (e) { // OK
+    case TooFew.e:
       print('e');
   }
 }
 
-// Not Enum-like
+// Not Enum-like.
 class PublicCons {
   const PublicCons();
   static const e = PublicCons();
@@ -94,20 +109,66 @@
 }
 
 void p(PublicCons e) {
-  switch(e) { // OK
-    case PublicCons.e :
+  switch (e) { // OK
+    case PublicCons.e:
       print('e');
   }
 }
 
-// Handled by analyzer
-enum ActualEnum {
-  e, f
-}
+// Handled by analyzer.
+enum ActualEnum { e, f }
+
 void ae(ActualEnum e) {
   // ignore: missing_enum_constant_in_switch
-  switch(e) { // OK
-    case ActualEnum.e :
+  switch (e) { // OK
+    case ActualEnum.e:
       print('e');
   }
 }
+
+// Some fields are deprecated.
+class DeprecatedFields {
+  final int i;
+  const DeprecatedFields._(this.i);
+
+  @deprecated
+  static const oldFoo = newFoo;
+  static const newFoo = DeprecatedFields._(1);
+  static const bar = DeprecatedFields._(2);
+  static const baz = DeprecatedFields._(3);
+}
+
+void dep(DeprecatedFields e) {
+  switch (e) { // OK
+    case DeprecatedFields.newFoo:
+      print('newFoo');
+      break;
+    case DeprecatedFields.bar:
+      print('bar');
+      break;
+    case DeprecatedFields.baz:
+      print('baz');
+      break;
+  }
+
+  switch (e) { // LINT
+    case DeprecatedFields.newFoo:
+      print('newFoo');
+      break;
+    case DeprecatedFields.baz:
+      print('baz');
+      break;
+  }
+
+  switch (e) { // OK
+    case DeprecatedFields.oldFoo:
+      print('oldFoo');
+      break;
+    case DeprecatedFields.bar:
+      print('bar');
+      break;
+    case DeprecatedFields.baz:
+      print('baz');
+      break;
+  }
+}
diff --git a/test_data/rules/experiments/nnbd/rules/avoid_init_to_null.dart b/test_data/rules/experiments/nnbd/rules/avoid_init_to_null.dart
index 37c97b6..0d918c3 100644
--- a/test_data/rules/experiments/nnbd/rules/avoid_init_to_null.dart
+++ b/test_data/rules/experiments/nnbd/rules/avoid_init_to_null.dart
@@ -4,7 +4,6 @@
 
 // test w/ `dart test -N avoid_init_to_null`
 
-int i = null; //OK (compilation error)
 int? ii = null; //LINT
 dynamic iii = null; //LINT
 
@@ -23,22 +22,13 @@
   final nil2 = null; //OK
 
   // TODO(pq): ints are not nullable so we'll want to update the lint here
-  // since it will produce a compilation error.
-  int x = null; //OK (compilation error)
   int? xx = null; //LINT
   int y; //OK
   int z; //OK
 
-  X({int a: null}) //OK (compilation error)
-    : y = 1, z = 1;
-
-  X.b({this.z: null}) //OK (compilation error)
-    : y = 1;
-
   X.c({this.xx: null}) //LINT
       : y = 1, z = 1;
 
-
   fooNamed({
     p: null, //LINT
     p1 = null, //LINT
diff --git a/test_data/rules/experiments/nnbd/rules/null_check_on_nullable_type_parameter.dart b/test_data/rules/experiments/nnbd/rules/null_check_on_nullable_type_parameter.dart
deleted file mode 100644
index b229cc0..0000000
--- a/test_data/rules/experiments/nnbd/rules/null_check_on_nullable_type_parameter.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N null_check_on_nullable_type_parameter`
-
-T m1<T>(T p) => p!; // OK
-T m2a<T>(T? p) => p!; // LINT
-dynamic m2b<T>(T? p) => p!; // OK
-T m3<T extends Object>(T? p) => p!; // OK
-T m4<T extends Object?>(T? p) => p!; // LINT
-T m5<T extends dynamic>(T? p) => p!; // LINT
-T m6<T>(T? p) => p!.a; // OK
-T m7<T>(T? p) => p!.m(); // OK
-T m8<T>(T? p) => p!..m(); // OK
-
-T m10<T>(T? p) { return p!; } // LINT
-T m20<T>(T? p) { T t = p!; } // LINT
-T m30<T>(T? p) {
-  T t;
-  t = p!; // LINT
-}
-class C<T> {
-  T t;
-  m(T? p) {
-    t = p!; // LINT
-  }
-}
-
-R m<P, R>(P? p) => p!; // OK
diff --git a/test_data/rules/experiments/nnbd/rules/null_closures.dart b/test_data/rules/experiments/nnbd/rules/null_closures.dart
deleted file mode 100644
index b88915a..0000000
--- a/test_data/rules/experiments/nnbd/rules/null_closures.dart
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N null_closures`
-
-import 'dart:async';
-import 'dart:core';
-
-class A extends B {
-  A(int x);
-}
-class B extends A {}
-
-//https://github.com/dart-lang/linter/issues/1414
-void test_cycle() {
-  new A(null);
-}
-
-void list_firstWhere() {
-  // firstWhere has a _named_ closure argument.
-  <int>[2, 4, 6].firstWhere((e) => e.isEven, orElse: null); // LINT
-  <int>[2, 4, 6].firstWhere((e) => e.isEven, orElse: () => null); // OK
-  <int>[2, 4, 6].where(null); // LINT
-  <int>[2, 4, 6].where((e) => e.isEven); // OK
-}
-
-void map_putIfAbsent() {
-  // putIfAbsent has a _required_ closure argument.
-  var map = <int, int>{};
-  map.putIfAbsent(7, null); // LINT
-  map.putIfAbsent(7, () => null); // OK
-}
-
-void future_wait() {
-  // Future.wait is a _static_ function with a _named_ argument.
-  Future.wait([], cleanUp: null); // LINT
-  Future.wait([], cleanUp: (_) => print('clean')); // OK
-}
-
-void list_generate() {
-  // todo (pq): look at migrated SDK and see if generators can be null.
-  // List.generate is a _constructor_ with a _positional_ argument.
-  new List.generate(3, null); // LINT
-  new List.generate(3, (_) => null); // OK
-}
-
-void map_otherMethods() {
-  // These methods have nothing we are concerned with.
-  new Map().keys; // OK
-  new Map().addAll({}); // OK
-}
diff --git a/test_data/rules/experiments/nnbd/rules/overridden_fields.dart b/test_data/rules/experiments/nnbd/rules/overridden_fields.dart
deleted file mode 100644
index 13bcfb7..0000000
--- a/test_data/rules/experiments/nnbd/rules/overridden_fields.dart
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N overridden_fields`
-
-class Base {
-  Object field = 'lorem';
-
-  Object something = 'change';
-}
-
-class Bad1 extends Base {
-  @override
-  final x = 1, field = 'ipsum'; // LINT
-}
-
-class Bad2 extends Base {
-  @override
-  Object something = 'done'; // LINT
-}
-
-class Bad3 extends Object with Base {
-  @override
-  Object something = 'done'; // LINT
-}
-
-class Ok extends Base {
-  Object newField; // OK
-
-  final Object newFinal = 'ignore'; // OK
-}
-
-class OK2 implements Base {
-  @override
-  Object something = 'done'; // OK
-
-  @override
-  Object field;
-}
-
-abstract class OK3 implements Base {
-  @override
-  Object something = 'done'; // OK
-}
-
-class GC11 extends Bad1 {
-  @override
-  Object something = 'done'; // LINT
-
-  Object gc33 = 'gc33';
-}
-
-abstract class GC12 implements Bad1 {
-  @override
-  Object something = 'done'; // OK
-}
-
-class GC13 extends Object with Bad1 {
-  @override
-  Object something = 'done'; // OK
-
-  @override
-  Object field = 'lint'; // LINT
-}
-
-abstract class GC21 extends GC11 {
-  @override
-  Object something = 'done'; // LINT
-}
-
-abstract class GC22 implements GC11 {
-  @override
-  Object something = 'done'; // OK
-}
-
-class GC23 extends Object with GC13 {
-  @override
-  Object something = 'done'; // LINT
-
-  @override
-  Object field = 'lint'; // LINT
-}
-
-class GC23_2 extends GC13 {
-  @override
-  var x = 7; // LINT
-}
-
-abstract class GC31 extends GC13 {
-  @override
-  Object something = 'done'; // LINT
-}
-
-abstract class GC32 implements GC13 {
-  @override
-  Object something = 'done'; // OK
-}
-
-class GC33 extends GC21 with GC13 {
-  @override
-  Object something = 'done'; // LINT
-
-  @override
-  Object gc33 = 'yada'; // LINT
-}
-
-class GC33_2 extends GC33 {
-  @override
-  var x = 3; // LINT
-
-  @override
-  Object gc33 = 'yada'; // LINT
-}
-
-class Super1 {}
-
-class Sub1 extends Super1 {
-  @override
-  int y;
-}
-
-class Super2 {
-  int x, y;
-}
-
-class Sub2 extends Super2 {
-  @override
-  int y; // LINT
-}
-
-class Super3 {
-  int x;
-}
-
-class Sub3 extends Super3 {
-  int x; // LINT
-}
-
-class A1 {
-  int f;
-}
-
-class B1 extends A1 {}
-
-abstract class C1 implements A1 {}
-
-class D1 extends B1 implements C1 {
-  @override
-  int f; // LINT
-}
-
-class A extends B {}
-class B extends A {
-  int field;
-}
-
-class StaticsNo {
-  static int a;
-}
-
-class VerifyStatic extends StaticsNo {
-  static int a;
-}
-
-mixin M on A1 {
-  @override
-  int f; // LINT
-
-  int g; // OK
-}
-
-abstract class BB {
-  abstract String s;
-}
-
-class AA extends BB {
-  /// Overriding abstracts in NNBD is OK.
-  @override
-  String s; // OK
-}
-
-class AAA with BB {
-  @override
-  String s; // OK
-}
-
-abstract class BBB {
-  abstract final String s;
-}
-
-class AAA extends BBB {
-  @override
-  String s; // OK
-}
diff --git a/test_data/rules/experiments/nnbd/rules/prefer_constructors_over_static_methods.dart b/test_data/rules/experiments/nnbd/rules/prefer_constructors_over_static_methods.dart
index 19bb993..1b7f9a8 100644
--- a/test_data/rules/experiments/nnbd/rules/prefer_constructors_over_static_methods.dart
+++ b/test_data/rules/experiments/nnbd/rules/prefer_constructors_over_static_methods.dart
@@ -37,7 +37,7 @@
 
   static Object ok() => Object(); // OK
 
-  static A? ok() => 1==1 ? null : A(); // OK
+  static A? ok2() => 1==1 ? null : A.internal(); // OK
 }
 
 class B<T> {
diff --git a/test_data/rules/experiments/nnbd/rules/type_init_formals.dart b/test_data/rules/experiments/nnbd/rules/type_init_formals.dart
index cfe5f43..185c3ca 100644
--- a/test_data/rules/experiments/nnbd/rules/type_init_formals.dart
+++ b/test_data/rules/experiments/nnbd/rules/type_init_formals.dart
@@ -6,10 +6,9 @@
 
 class A {
   String? p1;
-  String p2;
+  String p2 = '';
 
   A.w({required String this.p1}); // OK
   A.x({required String? this.p1}); // LINT
-  A.y({required String? this.p2}); // OK
   A.z({required String this.p2}); // LINT
 }
diff --git a/test_data/rules/experiments/nnbd/rules/unnecessary_null_checks.dart b/test_data/rules/experiments/nnbd/rules/unnecessary_null_checks.dart
deleted file mode 100644
index 44c1d40..0000000
--- a/test_data/rules/experiments/nnbd/rules/unnecessary_null_checks.dart
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N unnecessary_null_checks`
-
-int? i;
-
-int? j1 = i!; // LINT
-int? j2 = (i!); // LINT
-
-m1a(int? p) => m1a(i!); // LINT
-m1b(int? p) => m1b((i!)); // LINT
-
-m2a({required String s, int? p}) => m2a(p: i!, s: ''); // LINT
-m2b({required String s, int? p}) => m2b(p: (i!), s: ('')); // LINT
-
-class A {
-  A([int? p]) {
-    A(i!); // LINT
-    A((i!)); // LINT
-  }
-
-  m1a(int? p) => m1a(i!); // LINT
-  m1b(int? p) => m1b((i!)); // LINT
-
-  m2a({required String s, int? p}) => m2a(p: i!, s: ''); // LINT
-  m2b({required String s, int? p}) => m2b(p: (i!), s: ('')); // LINT
-
-  m3a(int? p) => p!; // OK
-  m3b(int? p) {
-    return p!; // OK
-  }
-
-  operator +(int? p) => A() + i!; // LINT
-  operator -(int? p) => A() + (i!); // LINT
-}
-
-int? f1(int? i) => i!; // LINT
-int? f2(int? i) { return i!; } // LINT
-
-f3(int? i) {
-  int? v;
-  v = i!; // LINT
-}
-
-f4(int? p) {
-  int? v;
-  v ??= 1;
-  v += p!; // OK
-}
-
-autoPromote() {
-  int? v2;
-  v2 = v2!; // OK
-}
-
-f5(int? p) {
-  int? v1;
-  v1 ??= p!; // OK
-}
-
-f6(int? p) {
-  return B() + p!; // OK
-}
-
diff --git a/test_data/rules/experiments/nnbd/rules/unrelated_type_equality_checks.dart b/test_data/rules/experiments/nnbd/rules/unrelated_type_equality_checks.dart
index 7a88737..d4c98af 100644
--- a/test_data/rules/experiments/nnbd/rules/unrelated_type_equality_checks.dart
+++ b/test_data/rules/experiments/nnbd/rules/unrelated_type_equality_checks.dart
@@ -4,7 +4,7 @@
 
 // test w/ `dart test -N unrelated_type_equality_checks`
 
-bool m(int? a1, num a2) {
+void m(int? a1, num a2) {
   var b1 = a1 == a2; // OK
   var b2 = a2 == a1; // OK
 }
diff --git a/test_data/rules/experiments/nonfunction-type-aliases/analysis_options.yaml b/test_data/rules/experiments/nonfunction-type-aliases/analysis_options.yaml
deleted file mode 100644
index 8576984..0000000
--- a/test_data/rules/experiments/nonfunction-type-aliases/analysis_options.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-analyzer:
-  enable-experiment:
-    - nonfunction-type-aliases
diff --git a/test_data/rules/experiments/nonfunction-type-aliases/rules/.mock_packages b/test_data/rules/experiments/nonfunction-type-aliases/rules/.mock_packages
deleted file mode 100644
index 4fc3349..0000000
--- a/test_data/rules/experiments/nonfunction-type-aliases/rules/.mock_packages
+++ /dev/null
@@ -1 +0,0 @@
-meta:../../../../mock_packages/meta/lib/
diff --git a/test_data/rules/experiments/nonfunction-type-aliases/rules/always_specify_types.dart b/test_data/rules/experiments/nonfunction-type-aliases/rules/always_specify_types.dart
deleted file mode 100644
index 471ae43..0000000
--- a/test_data/rules/experiments/nonfunction-type-aliases/rules/always_specify_types.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N always_specify_types`
-
-import 'package:meta/meta.dart';
-
-typedef MapList = List<StringMap>; //LINT
-typedef JsonMap = Map<String, dynamic>; //OK
-typedef StringList = List<JsonMap>; //OK
-typedef RawList = List; //LINT
-typedef StringMap<V> = Map<String, V>; //OK
-
-StringMap<String> sm = StringMap<String>(); //OK
-StringMap? rm1; //LINT
-StringMap<String> rm2 = StringMap(); //LINT
-StringMap rm3 = StringMap<String>(); //LINT
-
-Map<String, String> map = {}; //LINT
-List<String> strings = []; //LINT
-Set<String> set = {}; //LINT
-
-List? list; //LINT
-List<List>? lists; //LINT
-List<int> ints = <int>[1]; //OK
-
-final x = 1; //LINT [1:5]
-final int xx = 3;
-const y = 2; //LINT
-const int yy = 3;
-
-a(var x) {} //LINT
-b(s) {} //LINT [3:1]
-c(int x) {}
-d(final x) {} //LINT
-e(final int x) {}
-
-@optionalTypeArgs
-class P<T> { }
-
-@optionalTypeArgs
-void g<T>() {}
-
-//https://github.com/dart-lang/linter/issues/851
-void test() {
-  g<dynamic>(); //OK
-  g(); //OK
-}
-
-main() {
-  var x = ''; //LINT [3:3]
-  for (var i = 0; i < 10; ++i) {  //LINT [8:3]
-    print(i);
-  }
-  List<String> ls = <String>[];
-  ls.forEach((s) => print(s)); //LINT [15:1]
-  for (var l in ls) { //LINT [8:3]
-    print(l);
-  }
-  try {
-    for (final l in ls) { // LINT [10:5]
-      print(l);
-    }
-  } on Exception catch (ex) {
-    print(ex);
-  } catch (e) { // NO warning (https://codereview.chromium.org/1427223002/)
-    print(e);
-  }
-
-  var __; // LINT
-
-  listen((_) { // OK!
-    // ...
-  });
-
-  P p = new P(); //OK (optionalTypeArgs)
-}
-
-P doSomething(P p) //OK (optionalTypeArgs)
-{
-  return p;
-}
-
-listen(void onData(Object event)) {}
-
-var z; //LINT
-
-class Foo {
-  static var bar; //LINT
-  static final baz  = 1; //LINT
-  static final int bazz = 42;
-  var foo; //LINT
-  Foo(var bar); //LINT [7:3]
-  void f(List l) { } //LINT
-}
-
-void m() {
-  if ('' is Map) //OK {
-      {
-    print("won't happen");
-  }
-}
diff --git a/test_data/rules/experiments/nonfunction-type-aliases/rules/camel_case_types.dart b/test_data/rules/experiments/nonfunction-type-aliases/rules/camel_case_types.dart
deleted file mode 100644
index 593e0ff..0000000
--- a/test_data/rules/experiments/nonfunction-type-aliases/rules/camel_case_types.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N camel_case_types`
-
-class A {}
-
-class DB {}
-
-class FooBar {}
-
-class _Foo {}
-
-class Foo extends _Foo {}
-
-typedef bool predicate(); //LINT [14:9]
-
-typedef bool Predicate(); //OK
-
-class fooBar // LINT
-{}
-
-class Foo$Bar //OK
-{}
-
-class Foo_Bar //LINT
-{}
-
-class $FooBar //OK
-{}
-
-typedef foo = Foo; //LINT
-
-typedef F = Foo; //OK
-
-typedef f = void Function(); //LINT
-
-class c = Object with M; //LINT
diff --git a/test_data/rules/experiments/nonfunction-type-aliases/rules/prefer_mixin.dart b/test_data/rules/experiments/nonfunction-type-aliases/rules/prefer_mixin.dart
deleted file mode 100644
index b544813..0000000
--- a/test_data/rules/experiments/nonfunction-type-aliases/rules/prefer_mixin.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N prefer_mixin`
-
-import 'dart:collection';
-
-import 'dart:convert';
-
-class A {}
-
-class B extends Object with A {} // LINT
-
-mixin M {}
-
-class C with M {} // OK
-
-abstract class I with IterableMixin {} //OK
-
-abstract class L with ListMixin {} //OK
-
-abstract class MM with MapMixin {} //OK
-
-abstract class S with SetMixin {} //OK
-
-abstract class SCS with StringConversionSinkMixin {} //OK
-
-// nonfunction-type-aliases
-
-typedef AA = A;
-
-abstract class CC with AA { } // LINT
-
-typedef AAA = M;
-
-abstract class CCC with AAA { } //OK
diff --git a/test_data/rules/experiments/nonfunction-type-aliases/rules/slash_for_doc_comments.dart b/test_data/rules/experiments/nonfunction-type-aliases/rules/slash_for_doc_comments.dart
deleted file mode 100644
index 31289a6..0000000
--- a/test_data/rules/experiments/nonfunction-type-aliases/rules/slash_for_doc_comments.dart
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N slash_for_doc_comments`
-
-/** lib */ //LINT
-library test.rules.slash_for_doc_comments;
-
-/** My class */ //LINT
-class A {}
-
-/// OK
-class B {
-
-  /** B */ //LINT
-  B();
-
-  /** x */ //LINT
-  var x;
-
-  /** y */ //LINT
-  y() {
-    /** l */ //LINT
-    void l() {}
-  }
-}
-
-/** G */ //LINT
-enum G {
-  /** A */ //LINT
-  A,
-  B
-}
-
-/** f */ //LINT
-typedef bool F();
-
-/** f */ //LINT
-typedef F2 = bool Function();
-
-/** z */ //LINT
-z() => null;
-
-/* meh */ //OK
-class C {}
-
-/** D */ //LINT
-var D = String;
-
-/** Z */ //LINT
-class Z = B with C;
-
-/** M1 */ //LINT
-mixin M1 {}
-
-/* meh */ //OK
-mixin M2 {}
-
-/** Ext */ //LINT
-extension Ext on Object {
-  /** e */ // LINT
-  void e() { }
-}
-
-/** Unnamed */ //LINT
-extension on A { }
-
-/** O */ //LINT
-typedef O = Object;
-
-/** OM */ //LINT
-class OM = Object with M;
diff --git a/test_data/rules/experiments/super_parameters/analysis_options.yaml b/test_data/rules/experiments/super_parameters/analysis_options.yaml
new file mode 100644
index 0000000..16dd380
--- /dev/null
+++ b/test_data/rules/experiments/super_parameters/analysis_options.yaml
@@ -0,0 +1,3 @@
+analyzer:
+  enable-experiment:
+    - super-parameters
diff --git a/test_data/rules/experiments/super_parameters/rules/.mock_packages b/test_data/rules/experiments/super_parameters/rules/.mock_packages
new file mode 100644
index 0000000..236a8a8
--- /dev/null
+++ b/test_data/rules/experiments/super_parameters/rules/.mock_packages
@@ -0,0 +1 @@
+flutter:../../../../mock_packages/flutter/lib/
diff --git a/test_data/rules/experiments/super_parameters/rules/use_key_in_widget_constructors.dart b/test_data/rules/experiments/super_parameters/rules/use_key_in_widget_constructors.dart
new file mode 100644
index 0000000..66e7a12
--- /dev/null
+++ b/test_data/rules/experiments/super_parameters/rules/use_key_in_widget_constructors.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N use_key_in_widget_constructors`
+
+// todo(pq): re-enable (or migrate) when mocked Flutter packages can be resolved internally
+// see: https://github.com/dart-lang/linter/issues/3296
+
+// import 'package:flutter/foundation.dart';
+// import 'package:flutter/widgets.dart';
+//
+// /// https://github.com/flutter/flutter/issues/100297
+// class OtherWidget extends StatelessWidget {
+//   const OtherWidget({required super.key}); //OK
+//
+//   @override
+//   Widget build(BuildContext context) {
+//     return Container();
+//   }
+// }
+
diff --git a/test_data/rules/implicit_call_tearoffs.dart b/test_data/rules/implicit_call_tearoffs.dart
new file mode 100644
index 0000000..663f3ae
--- /dev/null
+++ b/test_data/rules/implicit_call_tearoffs.dart
@@ -0,0 +1,123 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N implicit_call_tearoffs`
+
+class C {
+  void call() {}
+  void other() {}
+}
+
+class C2 {
+  void call<T>(T arg) {}
+}
+
+void callIt(void Function() f) {
+  f();
+}
+
+void callIt2(void Function(int) f) {
+  f(0);
+}
+
+void Function() r1() => C(); // LINT
+void Function() r2() => C().call; // OK
+void Function() r3(C c) => c; // LINT
+void Function() r4(C c) => c.call; // OK
+
+void Function() r5(C? c1, C c2) {
+  return c1 ?? c2; // LINT
+}
+
+void Function() r6(C? c1, C c2) {
+  return c1?.call ?? c2.call; // OK
+}
+
+void Function() r7() {
+  return C()..other(); // LINT
+}
+
+void Function() r8() {
+  return (C()..other()).call; // OK
+}
+
+List<void Function()> r9(C c) {
+  return [c]; // LINT
+}
+
+List<void Function()> r10(C c) {
+  return [c.call]; // OK
+}
+
+void Function(int) r11(C2 c) => c; // LINT
+void Function(int) r12(C2 c) => c.call; // OK
+
+void main() {
+  callIt(C()); // LINT
+  callIt(C().call); // OK
+  Function f1 = C(); // LINT
+  Function f2 = C().call; // OK
+  void Function() f3 = C(); // LINT
+  void Function() f4 = C().call; // OK
+
+  final c = C();
+  callIt(c); // LINT
+  callIt(c.call); // OK
+  Function f5 = c; // LINT
+  Function f6 = c.call; // OK
+  void Function() f7 = c; // LINT
+  void Function() f8 = c.call; // OK
+
+  <void Function()>[
+    C(), // LINT
+    C().call, //OK
+    c, // LINT
+    c.call, // OK
+  ];
+
+  callIt2(C2()); // LINT
+  callIt2(C2().call); // OK
+  callIt2(C2()<int>); // LINT
+  callIt2(C2().call<int>); // OK
+  Function f9 = C2(); // LINT
+  Function f10 = C2().call; // OK
+  Function f11 = C2()<int>; // LINT
+  Function f12 = C2().call<int>; // OK
+  void Function<T>(T) f13 = C2(); // LINT
+  void Function<T>(T) f14 = C2().call; // OK
+  void Function(int) f15 = C2(); // LINT
+  void Function(int) f16 = C2().call; // OK
+  void Function(int) f17 = C2()<int>; // LINT
+  void Function(int) f18 = C2().call<int>; // OK
+
+  final c2 = C2();
+  callIt2(c2); // LINT
+  callIt2(c2.call); // OK
+  callIt2(c2<int>); // LINT
+  callIt2(c2.call<int>); // OK
+  Function f19 = c2; // LINT
+  Function f20 = c2.call; // OK
+  Function f21 = c2<int>; // LINT
+  Function f22 = c2.call<int>; // OK
+  void Function<T>(T) f23 = c2; // LINT
+  void Function<T>(T) f24 = c2.call; // OK
+  void Function(int) f25 = c2; // LINT
+  void Function(int) f26 = c2.call; // OK
+  void Function(int) f27 = c2<int>; // LINT
+  void Function(int) f28 = c2.call<int>; // OK
+
+  <void Function(int)>[
+    C2(), // LINT
+    C2().call, //OK
+    C2()<int>, // LINT
+    C2().call<int>, //OK
+    c2, // LINT
+    c2.call, // OK
+    c2<int>, // LINT
+    c2.call<int>, // OK
+  ];
+
+  C2()<int>; // LINT
+  c2<int>; // LINT
+}
diff --git a/test_data/rules/invariant_booleans.dart b/test_data/rules/invariant_booleans.dart
index 4f178ee..be8a27e 100644
--- a/test_data/rules/invariant_booleans.dart
+++ b/test_data/rules/invariant_booleans.dart
@@ -170,16 +170,16 @@
 int baz = 2;
 
 int foo = 0;
-bool setting;
+bool setting = false;
 
 class A {
-  bool foo;
-  int fooNumber;
+  bool foo = false;
+  int fooNumber = 0;
 }
 
 class B {
-  bool bar;
-  int barNumber;
+  bool bar = false;
+  int barNumber = 0;
 }
 
 A a = new A();
@@ -358,7 +358,7 @@
 }
 
 class Foo {
-  bool bar;
+  bool bar = false;
   void sayHello() {
     if (bar ?? false) print('hello');
   }
@@ -453,7 +453,7 @@
 }
 
 void bug658() {
-  String text;
+  String? text;
   if ((text?.length ?? 0) != 0) {}
 }
 
diff --git a/test_data/rules/iterable_contains_unrelated_type.dart b/test_data/rules/iterable_contains_unrelated_type.dart
index 3464de1..98e51b9 100644
--- a/test_data/rules/iterable_contains_unrelated_type.dart
+++ b/test_data/rules/iterable_contains_unrelated_type.dart
@@ -7,6 +7,9 @@
 void someFunction() {
   var list = <int>[];
   if (list.contains('1')) print('someFunction'); // LINT
+  list
+    ..add(1)
+    ..contains('1'); // LINT
 }
 
 void someFunction1() {
@@ -25,49 +28,51 @@
 }
 
 void someFunction4_1() {
-  List list;
-  if (list.contains(null)) print('someFucntion4_1');
+  List<dynamic> list = [];
+  if (list.contains(null)) print('someFunction4_1');
 }
 
 void someFunction5_1() {
   List<ClassBase> list = <ClassBase>[];
-  Object instance;
+  Object instance = '';
   if (list.contains(instance)) print('someFunction5_1'); // OK
 }
 
 void someFunction5() {
   List<ClassBase> list = <ClassBase>[];
-  DerivedClass1 instance;
+  DerivedClass1 instance = DerivedClass1();
   if (list.contains(instance)) print('someFunction5'); // OK
 }
 
 void someFunction6() {
   List<Mixin> list = <Mixin>[];
-  DerivedClass2 instance;
+  DerivedClass2 instance = DerivedClass2();
   if (list.contains(instance)) print('someFunction6'); // OK
 }
 
+class MixedIn with Mixin {}
+
 void someFunction6_1() {
   List<DerivedClass2> list = <DerivedClass2>[];
-  Mixin instance;
+  Mixin instance = MixedIn();
   if (list.contains(instance)) print('someFunction6_1'); // OK
 }
 
 void someFunction7() {
   List<Mixin> list = <Mixin>[];
-  DerivedClass3 instance;
+  DerivedClass3 instance = DerivedClass3();
   if (list.contains(instance)) print('someFunction7'); // OK
 }
 
 void someFunction7_1() {
   List<DerivedClass3> list = <DerivedClass3>[];
-  Mixin instance;
+  Mixin instance = MixedIn();
   if (list.contains(instance)) print('someFunction7_1'); // OK
 }
 
 void someFunction8() {
   List<DerivedClass2> list = <DerivedClass2>[];
-  DerivedClass3 instance;
+  DerivedClass3 instance = DerivedClass3();
   if (list.contains(instance)) print('someFunction8'); // OK
 }
 
@@ -92,7 +97,7 @@
 
 void someFunction12() {
   List<DerivedClass4> list = <DerivedClass4>[];
-  DerivedClass5 instance;
+  DerivedClass5 instance = DerivedClass5();
   if (list.contains(instance)) print('someFunction12'); // LINT
 }
 
@@ -176,3 +181,25 @@
   bool myConcreteBadMethod(String thing) => this.contains(thing); // LINT
   bool myConcreteBadMethod1(String thing) => contains(thing); // LINT
 }
+
+enum E implements List<int> {
+  one,
+  two;
+
+  @override
+  dynamic noSuchMethod(_) => throw UnsupportedError('');
+
+  void f() {
+    contains('string'); // LINT
+    this.contains('string'); // LINT
+    contains(1); // OK
+  }
+}
+
+extension on List<int> {
+  void f() {
+    contains('string'); // LINT
+    this.contains('string'); // LINT
+    contains(1); // OK
+  }
+}
diff --git a/test_data/rules/join_return_with_assignment.dart b/test_data/rules/join_return_with_assignment.dart
index bab26e8..cc9b53c 100644
--- a/test_data/rules/join_return_with_assignment.dart
+++ b/test_data/rules/join_return_with_assignment.dart
@@ -24,15 +24,16 @@
 }
 
 class A {
-  int _a;
-  int get myA {
+  int? _a;
+  int b = 1;
+  int? get myA {
     _a ??= 0; // LINT
     return _a;
   }
 }
 
 class B {
-  int _a;
+  late int _a;
   int get myA {
     if (_a == 0) {
       _a = 10; // LINT
@@ -52,13 +53,13 @@
   final C c1 = C();
   final C c2 = C();
 
-  c2.a._a = 1; // OK
-  return c1.a._a;
+  c2.a.b = 1; // OK
+  return c1.a.b;
 }
 
 int joinablePropertyAccessReturn() {
   final C c1 = C();
 
-  c1.a._a = 1; // LINT
-  return c1.a._a;
+  c1.a.b = 1; // LINT
+  return c1.a.b;
 }
diff --git a/test_data/rules/library_names.dart b/test_data/rules/library_names.dart
deleted file mode 100644
index fd9fcd7..0000000
--- a/test_data/rules/library_names.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N library_names`
-
-library Foo; //LINT [9:3]
diff --git a/test_data/rules/library_prefixes.dart b/test_data/rules/library_prefixes.dart
index d3df9aa..e1672d0 100644
--- a/test_data/rules/library_prefixes.dart
+++ b/test_data/rules/library_prefixes.dart
@@ -13,6 +13,6 @@
 main() {
   _i1.print(dartMath.pi);
   _i1.print(_async.Timer);
-  _i1.print(new $collection.HashSet<_i1.String>());
-  _i1.print(_1.unicodeBomCharacterRune);
+  _i1.print(new $collection.LinkedHashSet<_i1.String>());
+  _i1.print(_1.json);
 }
diff --git a/test_data/rules/list_remove_unrelated_type.dart b/test_data/rules/list_remove_unrelated_type.dart
index d58675b..5959f3a 100644
--- a/test_data/rules/list_remove_unrelated_type.dart
+++ b/test_data/rules/list_remove_unrelated_type.dart
@@ -7,6 +7,9 @@
 void someFunction() {
   var list = <int>[];
   if (list.remove('1')) print('someFunction'); // LINT
+  list
+    ..add(1)
+    ..remove('1'); // LINT
 }
 
 void someFunction1() {
@@ -25,49 +28,51 @@
 }
 
 void someFunction4_1() {
-  List list;
-  if(list.remove(null)) print('someFunction4_1');
+  List list = [];
+  if (list.remove(null)) print('someFunction4_1');
 }
 
 void someFunction5_1() {
   List<ClassBase> list = <ClassBase>[];
-  Object instance;
+  Object instance = '';
   if (list.remove(instance)) print('someFunction5_1'); // OK
 }
 
 void someFunction5() {
   List<ClassBase> list = <ClassBase>[];
-  DerivedClass1 instance;
+  DerivedClass1 instance = DerivedClass1();
   if (list.remove(instance)) print('someFunction5'); // OK
 }
 
 void someFunction6() {
   List<Mixin> list = <Mixin>[];
-  DerivedClass2 instance;
+  DerivedClass2 instance = DerivedClass2();
   if (list.remove(instance)) print('someFunction6'); // OK
 }
 
+class MixedIn with Mixin {}
+
 void someFunction6_1() {
   List<DerivedClass2> list = <DerivedClass2>[];
-  Mixin instance;
+  Mixin instance = MixedIn();
   if (list.remove(instance)) print('someFunction6_1'); // OK
 }
 
 void someFunction7() {
   List<Mixin> list = <Mixin>[];
-  DerivedClass3 instance;
+  DerivedClass3 instance = DerivedClass3();
   if (list.remove(instance)) print('someFunction7'); // OK
 }
 
 void someFunction7_1() {
   List<DerivedClass3> list = <DerivedClass3>[];
-  Mixin instance;
+  Mixin instance = MixedIn();
   if (list.remove(instance)) print('someFunction7_1'); // OK
 }
 
 void someFunction8() {
   List<DerivedClass2> list = <DerivedClass2>[];
-  DerivedClass3 instance;
+  DerivedClass3 instance = DerivedClass3();
   if (list.remove(instance)) print('someFunction8'); // OK
 }
 
@@ -92,12 +97,13 @@
 
 void someFunction12() {
   List<DerivedClass4> list = <DerivedClass4>[];
-  DerivedClass5 instance;
+  DerivedClass5 instance = DerivedClass5();
   if (list.remove(instance)) print('someFunction12'); // LINT
 }
 
 void bug_267(list) {
-  if (list.remove('1')) print('someFunction'); // https://github.com/dart-lang/linter/issues/267
+  if (list.remove('1'))
+    print('someFunction'); // https://github.com/dart-lang/linter/issues/267
 }
 
 abstract class ClassBase {}
@@ -133,19 +139,27 @@
 bool takesList3(List list) => list.remove('a'); // OK
 
 abstract class A implements List<int> {}
+
 abstract class B extends A {}
+
 bool takesB(B b) => b.remove('a'); // LINT
 
 abstract class A1 implements List<String> {}
+
 abstract class B1 extends A1 {}
+
 bool takesB1(B1 b) => b.remove('a'); // OK
 
 abstract class A3 implements List {}
+
 abstract class B3 extends A3 {}
+
 bool takesB3(B3 b) => b.remove('a'); // OK
 
 abstract class A2 implements List<String> {}
+
 abstract class B2 extends A2 {}
+
 bool takesB2(B2 b) => b.remove('a'); // OK
 
 abstract class SomeList<E> implements List<E> {}
@@ -171,3 +185,25 @@
   bool myConcreteBadMethod(String thing) => this.remove(thing); // LINT
   bool myConcreteBadMethod1(String thing) => remove(thing); // LINT
 }
+
+enum E implements List<int> {
+  one,
+  two;
+
+  @override
+  dynamic noSuchMethod(_) => throw UnsupportedError('');
+
+  void f() {
+    remove('string'); // LINT
+    this.remove('string'); // LINT
+    remove(1); // OK
+  }
+}
+
+extension on List<int> {
+  void f() {
+    remove('string'); // LINT
+    this.remove('string'); // LINT
+    remove(1); // OK
+  }
+}
diff --git a/test_data/rules/literal_only_boolean_expressions.dart b/test_data/rules/literal_only_boolean_expressions.dart
index e49d5a4..63f7154 100644
--- a/test_data/rules/literal_only_boolean_expressions.dart
+++ b/test_data/rules/literal_only_boolean_expressions.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N literal_only_boolean_expressions`
 
+// ignore_for_file: dead_code
+
 bool variable = true;
 
 void bad() {
@@ -25,6 +27,6 @@
 bool m() => true;
 
 void bug658() {
-  String text;
+  String? text;
   if ((text?.length ?? 0) != 0) {}
 }
diff --git a/test_data/rules/missing_whitespace_between_adjacent_strings.dart b/test_data/rules/missing_whitespace_between_adjacent_strings.dart
index 726d369..5905272 100644
--- a/test_data/rules/missing_whitespace_between_adjacent_strings.dart
+++ b/test_data/rules/missing_whitespace_between_adjacent_strings.dart
@@ -18,7 +18,6 @@
   f('long line\t' 'is long'); // OK
 
   f(RegExp('(\n)+' '(\n)+' '(\n)+')); // OK
-  new Unresolved('aaa' 'bbb'); // OK
   matches('(\n)+' '(\n)+' '(\n)+'); // OK
 
   f('Hello' // OK
diff --git a/test_data/rules/no_leading_underscores_for_library_prefixes.dart b/test_data/rules/no_leading_underscores_for_library_prefixes.dart
new file mode 100644
index 0000000..043cfa2
--- /dev/null
+++ b/test_data/rules/no_leading_underscores_for_library_prefixes.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N no_leading_underscores_for_library_prefixes`
+
+import 'dart:async' as _async; // LINT
+import 'dart:convert' as _convert; // LINT
+import 'dart:core' as dart_core; // OK
+import 'dart:math' as dart_math; // OK
diff --git a/test_data/rules/no_leading_underscores_for_local_identifiers.dart b/test_data/rules/no_leading_underscores_for_local_identifiers.dart
new file mode 100644
index 0000000..16e6fbf
--- /dev/null
+++ b/test_data/rules/no_leading_underscores_for_local_identifiers.dart
@@ -0,0 +1,151 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N no_leading_underscores_for_local_identifiers`
+
+/// https://github.com/dart-lang/linter/issues/3360
+int _g() { // OK (not local)
+  int _bar() { // LINT
+    return 10;
+  }
+  var x = () {
+    _foo() { }; // LINT
+  };
+  return _bar();
+}
+
+class _A { // OK
+  int _a() { // OK
+    int _bar() => 10; // LINT
+    return _bar();
+  }
+}
+
+///https://github.com/dart-lang/linter/issues/3126
+void fn0() {
+  for (var _ in []) { } // OK
+  var _ = g(); //OK
+  for (var __ = 0; __ < 1; ++__) { } // OK
+}
+
+int g() => 0;
+
+/// https://github.com/dart-lang/linter/issues/3127
+class P {
+  final int _p;
+  const P([this._p = 7]); // OK
+}
+
+var _foo = 0; // OK
+const _foo1 = 1; // OK
+final _foo2 = 2; // OK
+
+void fn() {
+  try {
+  } catch(_) { } // OK
+  var _f1, // LINT
+      _f2; // LINT
+  const _foo1 = 1; // LINT
+  final _foo2 = 2; // LINT
+  var foo_value = 0; // OK
+  var foo__value = 0; // OK
+  var foo__value_ = 0; // OK
+  () {
+    var _f = 0; // LINT
+  }();
+}
+
+void fn2(_param1) => null; // LINT
+
+void fn3(param) => null; // OK
+
+void fn4(_) => null; // OK
+
+void fn5(param_value) => null; // OK
+
+void fn6(void Function() function) {
+  fn6(() {
+    var _v = 1; // LINT
+  });
+}
+
+class TestClass {
+  var _foo = 0; // OK
+  static const _foo1 = 1; // OK
+  final _foo2 = 2; // OK
+
+  void foo() {
+    var _foo = 0; // LINT
+    const _foo1 = 1; // LINT
+    final _foo2 = 2; // LINT
+    var foo_value = 0; // OK
+    var foo__value = 0; // OK
+    var foo__value_ = 0; // OK
+
+    for(var _x in [1,2,3]) {} // LINT
+    for(var x in [1,2,3]) {} // OK
+
+    [1,2,3].forEach((_x) => fn()); // LINT
+    [1,2,3].forEach((x) => fn()); // OK
+    [1,2,3].forEach((_) => fn()); // OK
+
+    for (var _i = 0; _i < [].length; ++_i) { } // LINT
+
+    for (var _i = 0, // LINT
+        _j = 0; // LINT
+        ;
+    ++_i, ++_j) {}
+
+    try {}
+    catch(_error) {} // LINT
+
+    try {}
+    catch(error) { // OK
+    }
+
+    try {}
+    catch(error, _stackTrace) {} // LINT
+
+    try {}
+    catch(error, stackTrace) { // OK
+    }
+
+    for (var _e in ['']) { // LINT
+    }
+
+    void bar(var _baz) { } // LINT
+  }
+
+  void foo1(_param) {} // LINT
+
+  void foo2(param) {} // OK
+
+  void foo3(_) {} // OK
+
+  void foo4(param_value) {} // OK
+
+  void foo5(param, [_positional]) {} // LINT
+
+  void foo6(param, [positional]) {} // OK
+
+  // ignore: private_optional_parameter
+  void foo7({required _named}) {} // OK
+
+  // ignore: private_optional_parameter
+  void foo8({_named}) {} // OK
+
+  void foo9({named}) {} // OK
+
+}
+
+typedef _OutputFunction = void Function(String msg); // OK
+
+class C {
+  int _i;
+  C(this._i);
+}
+
+class D extends C {
+  D(super._i); // OK
+}
diff --git a/test_data/rules/no_logic_in_create_state.dart b/test_data/rules/no_logic_in_create_state.dart
index 2366798..a040733 100644
--- a/test_data/rules/no_logic_in_create_state.dart
+++ b/test_data/rules/no_logic_in_create_state.dart
@@ -8,7 +8,7 @@
 import 'package:flutter/widgets.dart';
 
 class MyState extends State {
-  int field;
+  int field = 0;
 }
 
 class MyStatefulOK extends StatefulWidget {
@@ -24,7 +24,7 @@
 }
 
 
-MyState global;
+MyState global = MyState();
 
 class MyStatefulBad extends StatefulWidget {
   @override
diff --git a/test_data/rules/non_constant_identifier_names.dart b/test_data/rules/non_constant_identifier_names.dart
index 69983c2..cc35181 100644
--- a/test_data/rules/non_constant_identifier_names.dart
+++ b/test_data/rules/non_constant_identifier_names.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N non_constant_identifier_names`
 
+// ignore_for_file: unused_local_variable, unused_element, unused_field
+
 void main() {
   var XYZ = 1; // LINT
   var ABC = 1, // LINT
@@ -59,7 +61,6 @@
   A(this.bar_bar); //OK
   A.N(this.bar_bar); //OK
   A.Named(this.bar_bar); //LINT
-  factory A.Named2(String a) = A; //LINT
   A._Named(this.bar_bar); //LINT
   A.named_bar(this.bar_bar); //LINT
   A.namedBar(this.bar_bar); //OK
@@ -88,6 +89,11 @@
   }
 }
 
+class B {
+  B(String b);
+  factory B.Named2(String b) = B; //LINT
+}
+
 foo() {
   listen((_) {}); // OK!
   listen((__) {}); // OK!
@@ -101,4 +107,5 @@
 // Generic function syntax should be OK (#805).
 T scope<T>(T Function() run) {
   /* ... */
+  return run();
 }
diff --git a/test_data/rules/noop_primitive_operations.dart b/test_data/rules/noop_primitive_operations.dart
index edcda55..3093086 100644
--- a/test_data/rules/noop_primitive_operations.dart
+++ b/test_data/rules/noop_primitive_operations.dart
@@ -4,6 +4,28 @@
 
 // test w/ `dart test -N noop_primitive_operations`
 
+onStringInterpolations() {
+  var s = '${1.toString()}'; // LINT
+}
+
+onPrint() {
+  print(''); // OK
+  print(1.toString()); // LINT
+  print(null.toString()); // LINT
+
+  print(toString()); // OK
+}
+
+String toString() => '';
+
+class Ok {
+  f() {
+    // Can't use 'super' as an expression
+    var s = '${super.toString()}'; // OK
+    print(super.toString()); // OK
+  }
+}
+
 onString() {
   String? nullable;
   String v = 'hello';
diff --git a/test_data/rules/null_check_on_nullable_type_parameter.dart b/test_data/rules/null_check_on_nullable_type_parameter.dart
new file mode 100644
index 0000000..326ad9c
--- /dev/null
+++ b/test_data/rules/null_check_on_nullable_type_parameter.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N null_check_on_nullable_type_parameter`
+
+T m1<T>(T p) => p!; // OK
+T m2a<T>(T? p) => p!; // LINT
+dynamic m2b<T>(T? p) => p!; // OK
+T m3<T extends Object>(T? p) => p!; // OK
+T m4<T extends Object?>(T? p) => p!; // LINT
+T m5<T extends dynamic>(T? p) => p!; // LINT
+int m6<T>(T? p) => p!.hashCode; // OK
+String m7<T>(T? p) => p!.toString(); // OK
+T m8<T>(T? p) => p!..toString(); // OK
+
+T m10<T>(T? p) { return p!; } // LINT
+T? m20<T>(T? p) { T t = p!; } // LINT
+T m30<T>(T? p) {
+  T t;
+  t = p!; // LINT
+  return t;
+}
+Future<T> m40<T extends Object?>(T? p) async => await p!; // LINT
+Future<List<T>> m41<T extends Object?>(T? p) async => await [p!]; // LINT
+List<T> m50<T>(T? p) => [p!]; // LINT
+Set<T> m60<T>(T? p) => {p!}; // LINT
+Map<String, T> m71<T>(T? p) => {'': p!}; // LINT
+Map<T, String> m72<T>(T? p) => {p!: ''}; // LINT
+Iterable<T> m80<T>(T? p) sync* {yield p!;} // LINT
+Stream<T> m90<T>(T? p) async* {yield p!;} // LINT
+class C<T> {
+  late T t;
+  m(T? p) {
+    t = p!; // LINT
+  }
+}
+
+R m<P, R>(P? p) => p! as R; // OK
diff --git a/test_data/rules/null_closures.dart b/test_data/rules/null_closures.dart
index 2c57116..3dea1b7 100644
--- a/test_data/rules/null_closures.dart
+++ b/test_data/rules/null_closures.dart
@@ -7,34 +7,21 @@
 import 'dart:async';
 import 'dart:core';
 
-class A extends B {
-  A(int x);
-}
-class B extends A {}
-
-//https://github.com/dart-lang/linter/issues/1414
-void test_cycle() {
-  new A(null);
-}
-
 void list_firstWhere() {
   // firstWhere has a _named_ closure argument.
   <int>[2, 4, 6].firstWhere((e) => e.isEven, orElse: null); // LINT
-  <int>[2, 4, 6].firstWhere((e) => e.isEven, orElse: () => null); // OK
-  <int>[2, 4, 6].where(null); // LINT
   <int>[2, 4, 6].where((e) => e.isEven); // OK
 }
 
 void iterable_singleWhere() {
   // singleWhere has a _named_ closure argument.
   <int>{2, 4, 6}.singleWhere((e) => e.isEven, orElse: null); // LINT
-  <int>[2, 4, 6].singleWhere((e) => e.isEven, orElse: () => null); // OK
+  <int?>[2, 4, 6].singleWhere((e) => e?.isEven ?? false, orElse: () => null); // OK
 }
 
 void map_putIfAbsent() {
   // putIfAbsent has a _required_ closure argument.
-  var map = <int, int>{};
-  map.putIfAbsent(7, null); // LINT
+  var map = <int, int?>{};
   map.putIfAbsent(7, () => null); // OK
 }
 
@@ -46,7 +33,6 @@
 
 void list_generate() {
   // List.generate is a _constructor_ with a _positional_ argument.
-  new List.generate(3, null); // LINT
   new List.generate(3, (_) => null); // OK
 }
 
diff --git a/test_data/rules/omit_local_variable_types.dart b/test_data/rules/omit_local_variable_types.dart
index a822b00..5c4e77e 100644
--- a/test_data/rules/omit_local_variable_types.dart
+++ b/test_data/rules/omit_local_variable_types.dart
@@ -4,7 +4,7 @@
 
 // test w/ `dart test -N omit_local_variable_types`
 
-// ignore_for_file: prefer_foreach
+// ignore_for_file: prefer_foreach, unused_local_variable
 
 f() {
   dynamic x = 0; // OK
@@ -54,7 +54,7 @@
 
   for (Person person in people) { // LINT
     peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
-    peopleByZip[person.zip].add(person);
+    peopleByZip[person.zip]!.add(person);
   }
 
   return peopleByZip;
@@ -65,7 +65,7 @@
 
   for (final person in people) { // OK
     peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
-    peopleByZip[person.zip].add(person);
+    peopleByZip[person.zip]!.add(person);
   }
 
   return peopleByZip;
@@ -81,10 +81,14 @@
 }
 
 class LinkedListNode {
-  LinkedListNode next;
+  late LinkedListNode next;
 }
 void traverse(LinkedListNode head) {
-  for (LinkedListNode node = head; node != null; node = node.next) { // LINT
+  for (LinkedListNode node = head; ; node = node.next) { // LINT
     // doSomething
   }
 }
+
+void bug2708() {
+  const Null response = null;
+}
diff --git a/test_data/rules/one_member_abstracts.dart b/test_data/rules/one_member_abstracts.dart
index ac95784..0150824 100644
--- a/test_data/rules/one_member_abstracts.dart
+++ b/test_data/rules/one_member_abstracts.dart
@@ -9,7 +9,7 @@
 }
 
 abstract class Y {
-  int x;
+  int x = 0;
   int f();
 }
 
diff --git a/test_data/rules/overridden_fields.dart b/test_data/rules/overridden_fields.dart
index e93667c..63174ce 100644
--- a/test_data/rules/overridden_fields.dart
+++ b/test_data/rules/overridden_fields.dart
@@ -11,7 +11,6 @@
 }
 
 class Bad1 extends Base {
-  @override
   final x = 1, field = 'ipsum'; // LINT
 }
 
@@ -26,7 +25,7 @@
 }
 
 class Ok extends Base {
-  Object newField; // OK
+  Object newField = 0; // OK
 
   final Object newFinal = 'ignore'; // OK
 }
@@ -36,7 +35,7 @@
   Object something = 'done'; // OK
 
   @override
-  Object field;
+  Object field = 0;
 }
 
 abstract class OK3 implements Base {
@@ -56,89 +55,36 @@
   Object something = 'done'; // OK
 }
 
-class GC13 extends Object with Bad1 {
-  @override
-  Object something = 'done'; // OK
-
-  @override
-  Object field = 'lint'; // LINT
-}
-
-abstract class GC21 extends GC11 {
-  @override
-  Object something = 'done'; // LINT
-}
-
 abstract class GC22 implements GC11 {
   @override
   Object something = 'done'; // OK
 }
 
-class GC23 extends Object with GC13 {
-  @override
-  Object something = 'done'; // LINT
-
-  @override
-  Object field = 'lint'; // LINT
-}
-
-class GC23_2 extends GC13 {
-  @override
-  var x = 7; // LINT
-}
-
-abstract class GC31 extends GC13 {
-  @override
-  Object something = 'done'; // LINT
-}
-
-abstract class GC32 implements GC13 {
-  @override
-  Object something = 'done'; // OK
-}
-
-class GC33 extends GC21 with GC13 {
-  @override
-  Object something = 'done'; // LINT
-
-  @override
-  Object gc33 = 'yada'; // LINT
-}
-
-class GC33_2 extends GC33 {
-  @override
-  var x = 3; // LINT
-
-  @override
-  Object gc33 = 'yada'; // LINT
-}
-
 class Super1 {}
 
 class Sub1 extends Super1 {
-  @override
-  int y;
+  int y = 0;
 }
 
 class Super2 {
-  int x, y;
+  int x = 0, y = 0;
 }
 
 class Sub2 extends Super2 {
   @override
-  int y; // LINT
+  int y = 0; // LINT
 }
 
 class Super3 {
-  int x;
+  int x = 0;
 }
 
 class Sub3 extends Super3 {
-  int x; // LINT
+  int x = 0; // LINT
 }
 
 class A1 {
-  int f;
+  int f = 0;
 }
 
 class B1 extends A1 {}
@@ -147,25 +93,44 @@
 
 class D1 extends B1 implements C1 {
   @override
-  int f; // LINT
-}
-
-class A extends B {}
-class B extends A {
-  int field;
+  int f = 0; // LINT
 }
 
 class StaticsNo {
-  static int a;
+  static int a = 0;
 }
 
 class VerifyStatic extends StaticsNo {
-  static int a;
+  static int a = 0;
 }
 
 mixin M on A1 {
   @override
-  int f; // LINT
+  int f = 0; // LINT
 
-  int g; // OK
+  int g = 0; // OK
 }
+
+abstract class BB {
+  abstract String s;
+}
+
+class AA extends BB {
+  /// Overriding abstracts in NNBD is OK.
+  @override
+  String s = ''; // OK
+}
+
+class AAA with BB {
+  @override
+  String s = ''; // OK
+}
+
+abstract class BBB {
+  abstract final String s;
+}
+
+class AAAA extends BBB {
+  @override
+  String s = ''; // OK
+}
\ No newline at end of file
diff --git a/test_data/rules/package_prefixed_library_names.dart b/test_data/rules/package_prefixed_library_names.dart
index cb2798b..bcd0820 100644
--- a/test_data/rules/package_prefixed_library_names.dart
+++ b/test_data/rules/package_prefixed_library_names.dart
@@ -4,4 +4,5 @@
 
 // test w/ `dart test -N package_prefixed_library_names`
 
-library linter.not_where_it_should_be; //LINT
+// See: https://github.com/dart-lang/linter/issues/3395
+library linter.not_where_it_should_be; //FAILING
diff --git a/test_data/rules/parameter_assignments.dart b/test_data/rules/parameter_assignments.dart
index b6aaa85..6bdaa89 100644
--- a/test_data/rules/parameter_assignments.dart
+++ b/test_data/rules/parameter_assignments.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N parameter_assignments`
 
+// ignore_for_file: unused_local_variable
+
 void badFunction(int parameter) {
   parameter = 4; // LINT
 }
@@ -30,7 +32,7 @@
   }
 }
 
-void ok2(String parameter) {
+void ok2(String? parameter) {
   if (parameter == null) {
     int parameter = 2;
     parameter = 3;
@@ -50,33 +52,36 @@
   print(parameter);
 }
 
-void actuallyGood(int required, {int optional}) { // OK
+void actuallyGood(int required, {int? optional}) { // OK
   optional ??= 8;
 }
 
-void actuallyGoodPositional(int required, [int optional]) { // OK
+void actuallyGoodPositional(int required, [int? optional]) { // OK
   optional ??= 8;
 }
 
-void butNotTwice(int required, [int optional]) {
+void butNotTwice(int required, [int? optional]) {
   optional ??= 8;
+  // ignore: dead_null_aware_expression
   optional ??= 16; // LINT
 }
 
-void onceAgainBad01(int required, {int optional}) {
+void onceAgainBad01(int required, {int? optional}) {
   optional ??= 8;
   optional = 42; // LINT
 }
 
-void onceAgainBad01Positional(int required, [int optional]) {
+void onceAgainBad01Positional(int required, [int? optional]) {
   optional ??= 8;
   optional = 42; // LINT
 }
 
 void onceAgainBad02(int required, {int optional: 42}) {
+  // ignore: dead_null_aware_expression
   optional ??= 8; // LINT
 }
 
 void onceAgainBad02Positional(int required, [int optional = 42]) {
+  // ignore: dead_null_aware_expression
   optional ??= 8; // LINT
 }
diff --git a/test_data/rules/prefer_asserts_in_initializer_lists.dart b/test_data/rules/prefer_asserts_in_initializer_lists.dart
index 300d09b..3d11dfa 100644
--- a/test_data/rules/prefer_asserts_in_initializer_lists.dart
+++ b/test_data/rules/prefer_asserts_in_initializer_lists.dart
@@ -26,13 +26,9 @@
   // still lint after unmovable assert
   A.c4(a) {
     assert(a != null); // LINT
-    assert(this != null); // OK
     assert(a != null); // LINT
   }
-  // no lint if this is used
-  A.c5(a) {
-    assert(this != null); // OK
-  }
+
   // no lint if field is used
   A.c6(a) {
     assert(this.f != null); // OK
@@ -59,6 +55,7 @@
   }
   factory A.c11({f}) {
     assert(f != null); // OK
+    return A.c1(1);
   }
   // lint for call of top level member
   A.c12() {
@@ -73,14 +70,6 @@
     assert(sa != null); // LINT
     assert(sm() != null); // LINT
   }
-
-  A.c14() {
-    assert(() // OK
-        {
-      f = true;
-      return false;
-    });
-  }
 }
 
 // no lint for super class attributes
@@ -107,14 +96,6 @@
   }
 }
 
-class E {
-  set tlg(v) {}
-  E() {
-    // setter with the same name as top level getter used
-    assert(tlg != null); // LINT
-  }
-}
-
 class F<T> {
   bool get foo => true;
 }
diff --git a/test_data/rules/prefer_bool_in_asserts.dart b/test_data/rules/prefer_bool_in_asserts.dart
index 13b13cc..786c67d 100644
--- a/test_data/rules/prefer_bool_in_asserts.dart
+++ b/test_data/rules/prefer_bool_in_asserts.dart
@@ -6,15 +6,12 @@
 
 // test w/ `dart test -N prefer_bool_in_asserts`
 
+// todo(pq): remove w/ lint-- https://github.com/dart-lang/linter/issues/3002
 main() {
   assert(true); // OK
   assert(true, 'message'); // OK
-  assert(() => true); // LINT
-  assert(() => true, 'message'); // LINT
   assert((() => true)()); // OK
   assert((() => true)(), 'message'); // OK
-  assert(() { return true; }); // LINT
-  assert(() { return true; }, 'message'); // LINT
   assert(() { return true; }()); // OK
   assert(() { return true; }(), 'message'); // OK
   assert(() { throw ""; }()); // OK
@@ -25,15 +22,3 @@
 m2(Object p) {
   assert(() { return p; }()); // OK
 }
-m3<T>(T p) {
-  assert(() { return p; }()); // OK
-}
-m4<T extends List>(T p) {
-  assert(() { return p; }()); // LINT
-}
-m5<S, T extends S>(T p) {
-  assert(() { return p; }()); // OK
-}
-m6<S extends List, T extends S>(T p) {
-  assert(() { return p; }()); // LINT
-}
diff --git a/test_data/rules/prefer_collection_literals.dart b/test_data/rules/prefer_collection_literals.dart
index 6ec462c..ba92825 100644
--- a/test_data/rules/prefer_collection_literals.dart
+++ b/test_data/rules/prefer_collection_literals.dart
@@ -8,7 +8,6 @@
 
 //ignore_for_file: unused_local_variable
 void main() {
-  var listToLint = new List(); //LINT
   var mapToLint = new Map(); // LINT
   var LinkedHashMapToLint = new LinkedHashMap(); // LINT
 
@@ -16,9 +15,7 @@
   var m2 = Map.fromIterable([]); //OK
   var m3 = Map.fromIterables([], []); //OK
 
-  var constructedListInsideLiteralList = [[], new List()]; // LINT
   var literalListInsideLiteralList = [[], []]; // OK
-  var fiveLengthList = new List(5); // OK
 
   var namedConstructorList = new List.filled(5, true); // OK
   var namedConstructorMap = new Map.identity(); // OK
@@ -60,10 +57,8 @@
   printHashSet(LinkedHashSet<int>()); // OK
   printIndentedHashSet(0, LinkedHashSet<int>()); // OK
 
-  printUnresolved(LinkedHashSet<int>()); // OK
-
-  Set<int> ss7 = LinkedHashSet.from([1, 2, 3]); // LINT
-  LinkedHashSet<int> ss8 =  LinkedHashSet.from([1, 2, 3]); // OK
+  Set<int> ss8 = LinkedHashSet.from([1, 2, 3]); // LINT
+  LinkedHashSet<int> ss9 =  LinkedHashSet.from([1, 2, 3]); // OK
 
   Iterable iter = Iterable.empty(); // OK
   var sss = Set.from(iter); // OK
diff --git a/test_data/rules/prefer_conditional_assignment.dart b/test_data/rules/prefer_conditional_assignment.dart
index ce3bdb8..ab5f524 100644
--- a/test_data/rules/prefer_conditional_assignment.dart
+++ b/test_data/rules/prefer_conditional_assignment.dart
@@ -6,12 +6,12 @@
 
 String getFullUserName(Person person) {
   // Something expensive
-  return null;
+  return '';
 }
 
 class Person {
-  int x;
-  String _fullName;
+  int x = 1;
+  String? _fullName;
 
   void badWithBlock1() {
     if (_fullName == null) { // LINT
@@ -31,7 +31,7 @@
     }
   }
 
-  String get badWithMultipleBlocks1 {
+  String? get badWithMultipleBlocks1 {
     if (_fullName == null) { // LINT
       {
         _fullName = getFullUserName(this);
@@ -40,7 +40,7 @@
     return _fullName;
   }
 
-  String get badWithMultipleBlocks2 {
+  String? get badWithMultipleBlocks2 {
     if ((_fullName) == (null)) { // LINT
       {
         _fullName = getFullUserName(this);
@@ -49,7 +49,7 @@
     return _fullName;
   }
 
-  String get badWithMultipleBlocks3 {
+  String? get badWithMultipleBlocks3 {
     if ((_fullName == null)) { // LINT
       {
         _fullName = getFullUserName(this);
@@ -58,19 +58,19 @@
     return _fullName;
   }
 
-  String get badWithoutBlock1 {
+  String? get badWithoutBlock1 {
     if (_fullName == null) // LINT
       _fullName = getFullUserName(this);
     return _fullName;
   }
 
-  String get badWithoutBlock2 {
+  String? get badWithoutBlock2 {
     if ((_fullName) == (null)) // LINT
       _fullName = getFullUserName(this);
     return _fullName;
   }
 
-  String get badWithoutBlock3 {
+  String? get badWithoutBlock3 {
     if ((_fullName == null)) // LINT
       _fullName = getFullUserName(this);
     return _fullName;
@@ -113,8 +113,8 @@
     } else {}
   }
 
-  A a;
-  A b;
+  A a = A();
+  A b = A();
 
   void f() {
     if (a.i == null) { // OK
@@ -130,5 +130,5 @@
 }
 
 class A {
-  int i;
+  int? i;
 }
diff --git a/test_data/rules/prefer_const_constructors.dart b/test_data/rules/prefer_const_constructors.dart
index 663eb42..2c76ed7 100644
--- a/test_data/rules/prefer_const_constructors.dart
+++ b/test_data/rules/prefer_const_constructors.dart
@@ -4,10 +4,10 @@
 
 // test w/ `dart test -N prefer_const_constructors`
 
-import 'package:meta/meta.dart';
+// ignore_for_file: unused_local_variable
 
 class A {
-  const A({A parent});
+  const A({A? parent});
   const A.a();
 }
 
@@ -68,7 +68,7 @@
 
   const F(this.l);
 
-  static F m1() => new F(null); // LINT
+  static F m1() => new F([]); // LINT
   static F m2(List<F> l) => new F(l); // OK
   static F m3(F f) => new F([f]); // OK
 }
@@ -78,7 +78,7 @@
 
   const G(this.m);
 
-  static G m1() => new G(null); // LINT
+  static G m1() => new G({}); // LINT
   static G m2(Map<G, G> m) => new G(m); // OK
   static G m3(G g) => new G({g: g}); // OK
 }
@@ -87,7 +87,7 @@
 class H {}
 class I {
   final H foo;
-  const I({this.foo});
+  const I({required this.foo});
 
   I makeI() => I(foo: H()); // OK
 }
@@ -98,12 +98,3 @@
 void gimmeJ<T>() => new J<T>(); // OK
 void gimmeJofString() => new J<String>(); // LINT
 void gimmeJofDynamic() => new J<dynamic>(); // LINT
-
-class K {
-  @literal
-  const K();
-}
-
-k() {
-  var kk = K(); // OK (handled by analyzer hint)
-}
diff --git a/test_data/rules/prefer_const_constructors_in_immutables.dart b/test_data/rules/prefer_const_constructors_in_immutables.dart
index 022220a..dd26224 100644
--- a/test_data/rules/prefer_const_constructors_in_immutables.dart
+++ b/test_data/rules/prefer_const_constructors_in_immutables.dart
@@ -33,6 +33,7 @@
 @immutable
 class D {
   final _a;
+  Object get a => _a;
   // no lint when there's a non const expression in initializer list
   D.c1(a) : _a = a.toString(); // OK
   D.c2(a) : _a = a; // LINT
@@ -51,8 +52,6 @@
 class F {
   const factory F.fc1() = F.c1; // OK
   factory F.fc2() = F.c1; // LINT
-  // no lint because const leads to error : Only redirecting factory constructors can be declared to be 'const'.
-  factory F.fc3() => null; // OK
   const F.c1();
 }
 
diff --git a/test_data/rules/prefer_const_declarations.dart b/test_data/rules/prefer_const_declarations.dart
index f2f65f3..f97ee39 100644
--- a/test_data/rules/prefer_const_declarations.dart
+++ b/test_data/rules/prefer_const_declarations.dart
@@ -6,6 +6,29 @@
 
 //ignore_for_file: unused_local_variable
 
+class C<T> {
+  C();
+  static C<X> m<X>() => C<X>();
+}
+
+void constructorTearOffTests<T>() {
+  final c1 = C.new; // LINT
+  final c2 = C<int>.new; // LINT
+  final C<int> Function() c3 = C.new; // LINT
+  // todo: consider cases -- https://github.com/dart-lang/linter/issues/2911
+  // final c4 = C<T>.new; // OK
+  // final C<T> Function() c5 = C.new; // OK
+}
+
+void constructorTearOffStaticTests<T>() {
+  final c1 = C.m; // LINT
+  // todo: consider cases -- https://github.com/dart-lang/linter/issues/2911
+  // final c2 = C.m<int>; // LINT
+  final C<int> Function() c3 = C.m; // LINT
+  final c4 = C.m<T>; // LINT
+  // final C<T> Function() c5 = C.m; // OK -- not constant
+}
+
 const o1 = const []; // OK
 final o2 = []; // OK
 final o3 = const []; // LINT
diff --git a/test_data/rules/prefer_const_literals_to_create_immutables.dart b/test_data/rules/prefer_const_literals_to_create_immutables.dart
index 01c6130..89a80b4 100644
--- a/test_data/rules/prefer_const_literals_to_create_immutables.dart
+++ b/test_data/rules/prefer_const_literals_to_create_immutables.dart
@@ -74,19 +74,11 @@
 var m14 = new A({1: ''}); // LINT
 var m15 = new A({1: null}); // LINT
 
-// ignore: undefined_class
-var e1 = new B([]); // OK
-
 // optional new
 class C {}
 var m16 = A([C()]); // OK
 
-@immutable
-class K {
-  final List<K> children;
-  const K({this.children});
+class B {
+  const B(b);
 }
-
-final k = K(
-  children: <K>[for (var i = 0; i < 5; ++i) K()], // OK
-);
+var bb = B([1]); //OK
diff --git a/test_data/rules/prefer_contains.dart b/test_data/rules/prefer_contains.dart
index 6ed2377..1ac710f 100644
--- a/test_data/rules/prefer_contains.dart
+++ b/test_data/rules/prefer_contains.dart
@@ -21,8 +21,6 @@
 
 bool le2 = [].indexOf(1) > -1; //LINT
 
-bool le3 = ([].indexOf(1) as int) > -1; //LINT
-
 bool le4 = -1 < list.indexOf(1); //LINT
 
 bool le5 = [].indexOf(1) < MINUS_ONE; //LINT
@@ -39,13 +37,6 @@
 
 bool mixed = list.indexOf(1) + "a".indexOf("ab") > 0; //OK
 
-condition() {
-  final int a = list.indexOf(1) > -1 ? 2 : 3; //LINT
-  list..indexOf(1);
-  var next;
-  while ((next = list.indexOf('{')) != -1) {} //OK
-}
-
 bool le7 = [].indexOf(1) > 1; //OK
 
 testOperators() {
diff --git a/test_data/rules/prefer_equal_for_default_values.dart b/test_data/rules/prefer_equal_for_default_values.dart
index 6ee2664..a5ce076 100644
--- a/test_data/rules/prefer_equal_for_default_values.dart
+++ b/test_data/rules/prefer_equal_for_default_values.dart
@@ -4,7 +4,9 @@
 
 // test w/ `dart test -N prefer_equal_for_default_values`
 
-f1({a: 1}) => null; // LINT
+// As of 2.19, this is a warning and the lint is a no-op.
+
+f1({a: 1}) => null; // OK
 f2({a = 1}) => null; // OK
 f3([a = 1]) => null; // OK
 f4([a]) => null; // OK
diff --git a/test_data/rules/prefer_expression_function_bodies.dart b/test_data/rules/prefer_expression_function_bodies.dart
index 5e4b3ad..7d4fa15 100644
--- a/test_data/rules/prefer_expression_function_bodies.dart
+++ b/test_data/rules/prefer_expression_function_bodies.dart
@@ -14,8 +14,8 @@
 }
 
 class A {
-  int left, right;
-  bool one;
+  int left = 1, right = 1;
+  bool one = false;
 
   int bad() { // LINT
     return 1;
@@ -37,7 +37,7 @@
     return right - left;
   }
 
-  get goodWidth => right - left; // OK
+  int get goodWidth => right - left; // OK
 
   set goodWidth(int width) => right = left + width; // OK
 
diff --git a/test_data/rules/prefer_final_fields.dart b/test_data/rules/prefer_final_fields.dart
index 2b4da51..1761072 100644
--- a/test_data/rules/prefer_final_fields.dart
+++ b/test_data/rules/prefer_final_fields.dart
@@ -159,7 +159,7 @@
 }
 
 class F {
-  var _array = new List<int>(5); // LINT
+  var _array = []; // LINT
 
   void foo() {
     _array[0] = 3;
diff --git a/test_data/rules/prefer_final_locals.dart b/test_data/rules/prefer_final_locals.dart
index 2932613..7963728 100644
--- a/test_data/rules/prefer_final_locals.dart
+++ b/test_data/rules/prefer_final_locals.dart
@@ -20,3 +20,27 @@
   label = 'hello world';
   print(label);
 }
+
+void multiUnmutated() {
+  var unmutated1 = 'hello', unmutated2 = 'world'; // LINT
+  print(unmutated1);
+  print(unmutated2);
+}
+
+void multiUnmutatedWithType() {
+  String unmutated1 = 'hello', unmutated2 = 'world'; // LINT
+  print(unmutated1);
+  print(unmutated2);
+}
+
+void multiWithAMutation() {
+  var mutated = 'hello', unmutated = 'unmutated'; // OK
+  print(mutated);
+  mutated = 'world';
+  print(mutated);
+  print(unmutated);
+}
+
+class Coverage {
+  int testedLines = 0; // OK
+}
diff --git a/test_data/rules/prefer_final_parameters.dart b/test_data/rules/prefer_final_parameters.dart
index 5def919..fd2a96c 100644
--- a/test_data/rules/prefer_final_parameters.dart
+++ b/test_data/rules/prefer_final_parameters.dart
@@ -145,3 +145,11 @@
     return C.good(contentValue + other.contentValue);
   }
 }
+
+class InitializingFormals {
+  final String initialize;
+
+  InitializingFormals.okInitializingFormal(this.initialize); // OK
+
+  InitializingFormals.okInitializingFormalNamed({required this.initialize}); // OK
+}
diff --git a/test_data/rules/prefer_foreach.dart b/test_data/rules/prefer_foreach.dart
index cd2caaa..3a628c3 100644
--- a/test_data/rules/prefer_foreach.dart
+++ b/test_data/rules/prefer_foreach.dart
@@ -19,7 +19,7 @@
   }
 }
 
-Function func() => null;
+Function func() => () {};
 
 void foo3() {
   for (final a in <int>[1]) { //LINT
@@ -39,7 +39,7 @@
 }
 
 class WithThirdPartyMethods {
-  WithMethods x;
+  WithMethods x = WithMethods();
 
   void foo() {
     final myList = [];
@@ -50,7 +50,7 @@
 }
 
 class WithElementInTarget {
-  List<WithMethods> myList;
+  List<WithMethods> myList = [];
 
   void good() {
     for (final x in myList) { // OK because x is the target
diff --git a/test_data/rules/prefer_function_declarations_over_variables.dart b/test_data/rules/prefer_function_declarations_over_variables.dart
index 23255a6..cd87cb3 100644
--- a/test_data/rules/prefer_function_declarations_over_variables.dart
+++ b/test_data/rules/prefer_function_declarations_over_variables.dart
@@ -32,3 +32,13 @@
 
   bad3 = print; // OK
 }
+
+var ok2 = () {}; // OK
+
+final bad4 = () {}; // LINT
+
+class C {
+  var ok3 = () {}; // OK
+
+  final bad5 = () {}; // LINT
+}
diff --git a/test_data/rules/prefer_initializing_formals.dart b/test_data/rules/prefer_initializing_formals.dart
index 0fa5bdf..3c912e2 100644
--- a/test_data/rules/prefer_initializing_formals.dart
+++ b/test_data/rules/prefer_initializing_formals.dart
@@ -4,6 +4,25 @@
 
 // test w/ `dart test -N prefer_initializing_formals`
 
+class D {
+  /// https://github.com/dart-lang/linter/issues/2664
+  D(int initialValue) : value = initialValue;
+
+  int? value;
+}
+
+class C {
+  String? value;
+  C._();
+
+  /// https://github.com/dart-lang/linter/issues/2441
+  factory C.withValue(String? value) {
+    var c = C._();
+    c.value = value; // OK
+    return c;
+  }
+}
+
 class Foo {
   final bool isBlahEnabled;
   final List<String> whatever;
@@ -34,7 +53,7 @@
 }
 
 class SimpleBadCase {
-  num x, y;
+  num x = 0, y = 0;
   SimpleBadCase(num x, num y) {
     this.x = x; // LINT
     this.y = y; // LINT
@@ -47,7 +66,7 @@
 }
 
 class SimpleBadCaseWithOnlyOneLint {
-  num x, y;
+  num x, y = 0;
   SimpleBadCaseWithOnlyOneLint(this.x, num y) {
     this.y = y; // LINT
   }
@@ -55,7 +74,7 @@
 
 /// https://github.com/dart-lang/linter/issues/2605
 class RenamedFieldsForReadability {
-  num x, y;
+  num x = 0, y = 0;
   RenamedFieldsForReadability(num a, num b) {
     x = a; // OK
     y = b; // OK
@@ -64,8 +83,8 @@
 
 /// https://github.com/dart-lang/linter/issues/2605
 class RenamedFieldsForReadability2 {
-  num x;
-  num y;
+  num x = 0;
+  num y = 0;
   RenamedFieldsForReadability2(num a, num b) {
     x = a; // OK
     y = b; // OK
@@ -73,7 +92,7 @@
 }
 
 class NoFieldsJustSetters {
-  String name;
+  String name = '';
   NoFieldsJustSetters(num x, num y) {
     this.x = x; // OK
     this.y = y; // OK
@@ -88,12 +107,12 @@
 }
 
 class NoFieldsJustSettersWithoutThisAndWithOneGetter {
-  String name;
+  String name = '';
   NoFieldsJustSettersWithoutThisAndWithOneGetter(num a, num b) {
     x = a; // OK
     y = b; // OK
   }
-  get x {
+  num get x {
     return 0;
   }
 
@@ -107,7 +126,7 @@
 }
 
 class SuperCallWithConstructorParameters extends SimpleGoodCase {
-  num r, theta;
+  num r = 0, theta = 0;
   SuperCallWithConstructorParameters(num r, num theta)
       : super(r * cos(theta), r * sin(theta)) {
     this.r = r; // LINT
@@ -135,7 +154,7 @@
 
 class GoodCaseWithPrivateFields {
   // ignore: unused_field
-  num _x, _y;
+  num? _x, _y;
   GoodCaseWithPrivateFields(num x, num y) {
     this._x = x; // OK // This should be lint for other rule
     this._y = y; // OK // This should be lint for other rule
@@ -144,7 +163,7 @@
 
 class GoodCaseWithPrivateFieldsWithoutThis {
   // ignore: unused_field
-  num _x, _y;
+  num _x = 0, _y = 0;
   GoodCaseWithPrivateFieldsWithoutThis(num x, num y) {
     _x = x; // OK // This should be lint for other rule
     _y = y; // OK // This should be lint for other rule
@@ -168,7 +187,7 @@
 }
 
 class BadCaseWithTwoFieldsOneArgument {
-  num x, y;
+  num x = 0, y = 0;
   BadCaseWithTwoFieldsOneArgument(num x) {
     this.x = x; // LINT
     this.y = x; // OK
@@ -176,14 +195,14 @@
 }
 
 class GoodCaseWithTwoFieldsOneArgument {
-  num x, y;
+  num x = 0, y = 0;
   GoodCaseWithTwoFieldsOneArgument(this.x) {
     y = this.x; // OK
   }
 }
 
 class GoodCaseWithTwoFieldsOneArgumentWithoutThis {
-  num x, y;
+  num x = 0, y = 0;
   GoodCaseWithTwoFieldsOneArgumentWithoutThis(this.x) {
     y = x; // OK
   }
@@ -212,31 +231,31 @@
 }
 
 class BadCaseWithNamedArgs {
-  num x, y;
-  BadCaseWithNamedArgs({num x, num y = 1}) {
+  num? x, y;
+  BadCaseWithNamedArgs({num? x, num y = 1}) {
     this.x = x; // LINT
     this.y = y; // LINT
   }
 }
 
 class GoodCaseWithDifferentNamedArgs {
-  num x, y;
-  GoodCaseWithDifferentNamedArgs({num a, num b = 1}) {
+  num? x, y;
+  GoodCaseWithDifferentNamedArgs({num? a, num b = 1}) {
     this.x = a; // OK
     this.y = b; // OK
   }
 }
 
 class BadCaseWithNamedArgsInitializer {
-  num x, y;
-  BadCaseWithNamedArgsInitializer({num x, num y = 1})
+  num? x, y;
+  BadCaseWithNamedArgsInitializer({num? x, num y = 1})
       : this.x = x, // LINT
         this.y = y; // LINT
 }
 
 class GoodCaseWithDifferentNamedArgsInitializer {
-  num x, y;
-  GoodCaseWithDifferentNamedArgsInitializer({num a, num b = 1})
+  num? x, y;
+  GoodCaseWithDifferentNamedArgsInitializer({num? a, num b = 1})
       : this.x = a, // OK
         this.y = b; // OK
 }
diff --git a/test_data/rules/prefer_int_literals.dart b/test_data/rules/prefer_int_literals.dart
index 1343268..4fe8866 100644
--- a/test_data/rules/prefer_int_literals.dart
+++ b/test_data/rules/prefer_int_literals.dart
@@ -25,7 +25,7 @@
   double y = 7.1e2; // LINT
   double z = 7.576e2; // OK
   A(this.x);
-  namedDouble(String s, {double d}) {}
+  namedDouble(String s, {double d = 0.0}) {}
   namedDynamic(String s, {d}) {}
 }
 
diff --git a/test_data/rules/prefer_iterable_whereType.dart b/test_data/rules/prefer_iterable_whereType.dart
index c43a6e6..f93d448 100644
--- a/test_data/rules/prefer_iterable_whereType.dart
+++ b/test_data/rules/prefer_iterable_whereType.dart
@@ -38,7 +38,7 @@
   }
 }
 class B {
-  bool where(bool Function(Object e) f) => null;
+  bool where(bool Function(Object e) f) => false;
 
   m() {
     final o = new B();
diff --git a/test_data/rules/prefer_mixin.dart b/test_data/rules/prefer_mixin.dart
index d40868a..b544813 100644
--- a/test_data/rules/prefer_mixin.dart
+++ b/test_data/rules/prefer_mixin.dart
@@ -26,3 +26,12 @@
 
 abstract class SCS with StringConversionSinkMixin {} //OK
 
+// nonfunction-type-aliases
+
+typedef AA = A;
+
+abstract class CC with AA { } // LINT
+
+typedef AAA = M;
+
+abstract class CCC with AAA { } //OK
diff --git a/test_data/rules/prefer_spread_collections.dart b/test_data/rules/prefer_spread_collections.dart
index 6f0cf19..a392966 100644
--- a/test_data/rules/prefer_spread_collections.dart
+++ b/test_data/rules/prefer_spread_collections.dart
@@ -19,7 +19,7 @@
 
 // Control flow.
 
-bool condition;
+bool condition = false;
 var l5 = ['a']..addAll(condition ? things : const []); // LINT
 var l6 = ['a']..addAll(condition ? things : []); // LINT
 
@@ -31,5 +31,3 @@
   A()..addAll(['a']); // OK
 }
 
-const thangs = [];
-const cc = []..addAll(thangs); // OK -- don't show on invalid code
diff --git a/test_data/rules/prefer_typing_uninitialized_variables.dart b/test_data/rules/prefer_typing_uninitialized_variables.dart
index 9b49c5e..c86e26a 100644
--- a/test_data/rules/prefer_typing_uninitialized_variables.dart
+++ b/test_data/rules/prefer_typing_uninitialized_variables.dart
@@ -10,7 +10,7 @@
   final baz; // LINT
   var a = 5,
       b; // LINT
-  String d;
+  String? d;
 
   BadClass(this.baz);
 
diff --git a/test_data/rules/prefer_void_to_null.dart b/test_data/rules/prefer_void_to_null.dart
index 8b0b9a7..a3cebab 100644
--- a/test_data/rules/prefer_void_to_null.dart
+++ b/test_data/rules/prefer_void_to_null.dart
@@ -7,16 +7,56 @@
 // TODO(mfairhurst) test void with a prefix, except that causes bugs.
 // TODO(mfairhurst) test defining a class named Null (requires a 2nd file)
 
+// ignore_for_file: unused_local_variable
+
 import 'dart:async';
 import 'dart:core';
 import 'dart:core' as core;
 
+abstract class X {
+  dynamic get foo;
+}
+
+/// https://github.com/dart-lang/linter/issues/1523
+class Y extends X {
+  @override
+  Null get foo => null; // OK
+}
+
+/// https://github.com/dart-lang/linter/issues/2792
+class A<T> {
+  Future<T>? something() {}
+}
+
+class B<T> implements A<T> {
+  @override
+  Null something() {}  // OK
+}
+
+class C {
+  FutureOr<void>? something() {}
+}
+
+class D implements C {
+  @override
+  Null something() {}  // LINT
+}
+
+class E {
+  Never? something() {} // LINT
+}
+
+class F implements E {
+  @override
+  Null something() {}  // OK
+}
+
 void void_; // OK
 Null null_; // LINT
 core.Null core_null; // LINT
-Future<void> future_void; // OK
-Future<Null> future_null; // LINT
-Future<core.Null> future_core_null; // LINT
+Future<void>? future_void; // OK
+Future<Null>? future_null; // LINT
+Future<core.Null>? future_core_null; // LINT
 
 void void_f() {} // OK
 Null null_f() {} // LINT
@@ -25,21 +65,21 @@
 f_null(Null x) {} // LINT
 f_core_null(core.Null x) {} // LINT
 
-void Function(Null) voidFunctionNull; // OK
-Null Function() nullFunctionVoid; // OK
-Future<Null> Function() FutureNullFunction; // LINT
-void Function(Future<Null>) voidFunctionFutureNull; // LINT
+void Function(Null)? voidFunctionNull; // OK
+Null Function()? nullFunctionVoid; // OK
+Future<Null> Function()? FutureNullFunction; // LINT
+void Function(Future<Null>)? voidFunctionFutureNull; // LINT
 
 usage() {
   void void_; // OK
   Null null_; // LINT
   core.Null core_null; // LINT
-  Future<void> future_void; // OK
+  Future<void>? future_void; // OK
   Future<Null> future_null; // LINT
   Future<core.Null> future_core_null; // LINT
 
-  future_void.then<Null>((_) {}); // LINT
-  future_void.then<void>((_) {}); // OK
+  future_void?.then<Null>((_) {}); // LINT
+  future_void?.then<void>((_) {}); // OK
 }
 
 void inference() {
@@ -62,10 +102,10 @@
   <Null, Null>{}; // OK
   <int, Null>{1: null}; // LINT
   <String, Null>{"foo": null}; // LINT
-  <Object, Null>{null: null}; // LINT
+  <Object?, Null>{null: null}; // LINT
   <Null, int>{null: 1}; // LINT
   <Null, String>{null: "foo"}; // LINT
-  <Null, Object>{null: null}; // LINT
+  <Null, Object?>{null: null}; // LINT
   <Null, // LINT
       Null>{null: null}; // LINT
   <int, void>{}; // OK
@@ -77,10 +117,10 @@
   <void, void>{}; // OK
   <int, void>{1: null}; // OK
   <String, void>{"foo": null}; // OK
-  <Object, void>{null: null}; // OK
+  <Object?, void>{null: null}; // OK
   <void, int>{null: 1}; // OK
   <void, String>{null: "foo"}; // OK
-  <void, Object>{null: null}; // OK
+  <void, Object?>{null: null}; // OK
   <void, void>{null: null}; // OK
 
   // TODO(mfairhurst): is it worth handling more complex literals?
@@ -99,9 +139,9 @@
   void void_; // OK
   Null null_; // LINT
   core.Null core_null; // LINT
-  Future<void> future_void; // OK
-  Future<Null> future_null; // LINT
-  Future<core.Null> future_core_null; // LINT
+  Future<void>? future_void; // OK
+  Future<Null>? future_null; // LINT
+  Future<core.Null>? future_core_null; // LINT
 
   void void_f() {} // OK
   Null null_f() {} // LINT
@@ -114,12 +154,12 @@
     void void_; // OK
     Null null_; // LINT
     core.Null core_null; // LINT
-    Future<void> future_void; // OK
+    Future<void>? future_void; // OK
     Future<Null> future_null; // LINT
     Future<core.Null> future_core_null; // LINT
 
-    future_void.then<Null>((_) {}); // LINT
-    future_void.then<void>((_) {}); // OK
+    future_void?.then<Null>((_) {}); // LINT
+    future_void?.then<void>((_) {}); // OK
   }
 
   parameterNamedNull(Object Null) {
@@ -135,3 +175,6 @@
 class MemberNamedNull {
   final Null = null; // OK
 }
+
+extension _ on Null { // OK
+}
diff --git a/test_data/rules/require_trailing_commas.dart b/test_data/rules/require_trailing_commas.dart
index e3c5f49..dce51d7 100644
--- a/test_data/rules/require_trailing_commas.dart
+++ b/test_data/rules/require_trailing_commas.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// test w/ `dart test -N require_trailing_commas`
+
 class RequireTrailingCommasExample {
   RequireTrailingCommasExample.constructor1(Object param1, Object param2);
 
@@ -37,10 +39,7 @@
       : assert(true,
             'A very very very very very very very very long string'); // LINT
 
-  void operator [](Object param1, Object param2, Object param3, Object param4,
-      Object param5) {} // LINT
-
-  void method1(Object param1, Object param2, {Object param3, Object param4}) {}
+  void method1(Object param1, Object param2, {Object? param3, Object? param4}) {}
 
   void method2(
     Object param1,
@@ -53,29 +52,29 @@
   void method3(
     Object param1,
     Object param2, {
-    Object param3,
-    Object param4,
-    Object param5,
+    Object? param3,
+    Object? param4,
+    Object? param5,
   }) {}
 
   void method4(Object param1, Object param2, Object param3, Object param4,
       Object param5) {} // LINT
 
   void method5(Object param1, Object param2,
-      {Object param3, Object param4, Object param5}) {} // LINT
+      {Object? param3, Object? param4, Object? param5}) {} // LINT
 
   void method6(Object param1, Object param2,
-      {Object param3,
-      Object param4,
-      Object param5,
-      Object param6,
-      Object param7}) {} // LINT
+      {Object? param3,
+      Object? param4,
+      Object? param5,
+      Object? param6,
+      Object? param7}) {} // LINT
 
   void method7(Object param1, Object param2, Object param3,
       {Object namedParam = true}) {} // LINT
 
   void run() {
-    void test(Object param1, Object param2, {Object param3}) {}
+    void test(Object param1, Object param2, {Object? param3}) {}
 
     test('fits on one line, no need trailing comma', 'test');
 
@@ -244,6 +243,34 @@
       false,
       'a very very very very very very very very very long string',
     );
+
+    assert(() {
+      return true;
+    }());
+
+    assert(() {
+      return true;
+    }(), 'comment'); // LINT
+
+    assert(
+      () {
+        return true;
+      }(),
+      'comment',
+    );
+
+    dynamic f;
+    f((a, b, c) {
+      return true;
+    }(
+      '',
+      '',
+      '',
+    )); // LINT
+
+    f('''
+A multiline string
+      '''); // OK
   }
 }
 
diff --git a/test_data/rules/sized_box_for_whitespace.dart b/test_data/rules/sized_box_for_whitespace.dart
index 1f86235..ffc067c 100644
--- a/test_data/rules/sized_box_for_whitespace.dart
+++ b/test_data/rules/sized_box_for_whitespace.dart
@@ -4,6 +4,7 @@
 
 // test w/ `dart test -N sized_box_for_whitespace`
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
 Widget containerWithChild() {
@@ -60,7 +61,7 @@
 
 Widget emptyContainerWithKeyAndWidthAndHeight() {
   return Container( // LINT
-    key: null,
+    key: Key(''),
     width: 10,
     height: 10,
   );
diff --git a/test_data/rules/sized_box_shrink_expand.dart b/test_data/rules/sized_box_shrink_expand.dart
new file mode 100644
index 0000000..5e09354
--- /dev/null
+++ b/test_data/rules/sized_box_shrink_expand.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N sized_box_shrink_expand`
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+Widget sizedBoxWithZeroWidthZeroHeight() {
+  return SizedBox( // LINT
+    height: 0,
+    width: 0,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithInfiniteWidthInfiniteHeight() {
+  return SizedBox( // LINT
+    height: double.infinity,
+    width: double.infinity,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithInfiniteWidthzeroHeight() {
+  return SizedBox( // OK
+    height: 0,
+    width: double.infinity,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithZeroWidthInfiniteHeight() {
+  return SizedBox( // OK
+    height: double.infinity,
+    width: 0,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithMixedWidthsAndHeights() {
+  return SizedBox( // OK
+    height: 26,
+    width: 42,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithZeroWidth() {
+  return SizedBox( // OK
+    width: 0,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithInfiniteWidth() {
+  return SizedBox( // OK
+    width: double.infinity,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithZeroHeight() {
+  return SizedBox( // OK
+    height: 0,
+    child: Container(),
+  );
+}
+
+Widget sizedBoxWithInfiniteHeight() {
+  return SizedBox( // OK
+    height: double.infinity,
+    child: Container(),
+  );
+}
diff --git a/test_data/rules/slash_for_doc_comments.dart b/test_data/rules/slash_for_doc_comments.dart
index bb0be96..8498dfb 100644
--- a/test_data/rules/slash_for_doc_comments.dart
+++ b/test_data/rules/slash_for_doc_comments.dart
@@ -66,3 +66,10 @@
 /** Unnamed */ //LINT
 extension on A { }
 
+/** O */ //LINT
+typedef O = Object;
+
+mixin M {}
+
+/** OM */ //LINT
+class OM = Object with M;
diff --git a/test_data/rules/sort_child_properties_last.dart b/test_data/rules/sort_child_properties_last.dart
index 9a4b3d4..93d5565 100644
--- a/test_data/rules/sort_child_properties_last.dart
+++ b/test_data/rules/sort_child_properties_last.dart
@@ -6,7 +6,8 @@
 
 // ignore_for_file: prefer_expression_function_bodies
 
-import 'package:flutter/widgets.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
 
 class W0 extends Widget {
   @override
@@ -99,15 +100,15 @@
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      body: Center(
-        key: 0,
+      body: RawMaterialButton(
+        key: Key(''),
         child: Center( // OK
           child: Column(
             key: 0,
             children: [],
           ),
         ),
-        onPress: () {
+        onPressed: () {
           // some code
         },
       ),
diff --git a/test_data/rules/sort_constructors_first.dart b/test_data/rules/sort_constructors_first.dart
index 7f61b7c..2b33069 100644
--- a/test_data/rules/sort_constructors_first.dart
+++ b/test_data/rules/sort_constructors_first.dart
@@ -21,11 +21,11 @@
 }
 
 abstract class D {
-  final a;
+  final a = 0;
   D(); //LINT
 }
 
 abstract class E {
-  static final a;
+  static final a = 0;
   E(); //LINT
 }
diff --git a/test_data/rules/super_goes_last.dart b/test_data/rules/super_goes_last.dart
index 1af1aa3..0036161 100644
--- a/test_data/rules/super_goes_last.dart
+++ b/test_data/rules/super_goes_last.dart
@@ -15,10 +15,3 @@
       : _b = a + 1,
         super(a); // OK
 }
-
-class C extends A {
-  int _c;
-  C(int a)
-      : super(a), // LINT [9:8]
-        _c = a + 1;
-}
diff --git a/test_data/rules/throw_in_finally.dart b/test_data/rules/throw_in_finally.dart
index 49ea2d0..45b1f6c 100644
--- a/test_data/rules/throw_in_finally.dart
+++ b/test_data/rules/throw_in_finally.dart
@@ -6,7 +6,7 @@
 
 class Ok {
   double compliantMethod() {
-    var i = 5;
+    var i = 5.0;
     try {
       i = 1 / 0;
     } catch (e) {
@@ -54,7 +54,7 @@
 
 class WeirdException {}
 
-Function registrationGuard;
+Function registrationGuard = () {};
 
 void outer() {
   try {
diff --git a/test_data/rules/type_init_formals.dart b/test_data/rules/type_init_formals.dart
index 9b56c9e..cff3008 100644
--- a/test_data/rules/type_init_formals.dart
+++ b/test_data/rules/type_init_formals.dart
@@ -25,6 +25,3 @@
   Good3(int this.a); // OK
 }
 
-class Invalid {
-  Invalid(int this.x); // OK
-}
diff --git a/test_data/rules/unawaited_futures.dart b/test_data/rules/unawaited_futures.dart
index 72067fa..480d5cb 100644
--- a/test_data/rules/unawaited_futures.dart
+++ b/test_data/rules/unawaited_futures.dart
@@ -6,7 +6,7 @@
 
 import 'dart:async';
 
-Future fut() => null;
+Future fut() => Future.value(0);
 
 foo1() {
   fut();
@@ -25,16 +25,17 @@
 
 foo4() async {
   var x = fut();
+  return x;
 }
 
 foo5() async {
+  Duration d = Duration();
   new Future.delayed(d); //LINT
-  new Future.delayed(d, bar);
 }
 
 foo6() async {
   var map = <String, Future>{};
-  map.putIfAbsent('foo', fut());
+  map.putIfAbsent('foo', () => fut());
 }
 
 foo7() async {
@@ -57,12 +58,12 @@
 
 foo10() async {
   _Foo()
-    ..futureListField[0] = fut();
+    ..futureListField?[0] = fut();
 }
 
 foo11() async {
   _Foo()
-    ..bar.futureField = fut();
+    ..bar?.futureField = fut();
 }
 
 foo12() async {
@@ -70,14 +71,18 @@
   x..[0] = fut();
 }
 
+foo13() async {
+  var y = '${fut()}'; //LINT
+}
+
 class _Bar {
-  Future<void> futureField;
+  Future<void>? futureField;
 }
 
 class _Foo {
-  Future<void> futureField;
-  List<Future<void>> futureListField;
-  _Bar bar;
+  Future<void>? futureField;
+  List<Future<void>>? futureListField = [];
+  _Bar? bar;
   Future<void> doAsync() async {}
   void doSync() => null;
   Future<void> get asyncProperty => doAsync();
@@ -85,9 +90,12 @@
 }
 
 /// https://github.com/dart-lang/linter/issues/2211
-class Future2 extends Future {}
+class Future2 implements Future {
+  @override
+  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
 
-Future2 fut2() => null;
+Future2 fut2() => Future2();
 
 f2() async {
   fut2(); //LINT
diff --git a/test_data/rules/unmocked/unsafe_html.dart b/test_data/rules/unmocked/unsafe_html.dart
new file mode 100644
index 0000000..f6ca561
--- /dev/null
+++ b/test_data/rules/unmocked/unsafe_html.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A trimmed version of `unsafe_html` used to validate that tests can be run
+/// successfully against a "real" and un-mocked SDK.
+import 'dart:html';
+
+void main() {
+  AnchorElement()
+    ..href = 'foo'; // LINT
+  var embed = EmbedElement();
+  embed.src = 'foo'; // LINT
+  IFrameElement()
+    ..src = 'foo'; // LINT
+
+  var script = ScriptElement();
+  script.src = 'foo.js'; // LINT
+  var src = 'foo.js'; // OK
+  var src2 = script.src; // OK
+  script
+    ..type = 'application/javascript'
+    ..src = 'foo.js'; // LINT
+  script
+    ..src = 'foo.js' // LINT
+    ..type = 'application/javascript';
+  script?.src = 'foo.js'; // LINT
+
+  // ...
+}
diff --git a/test_data/rules/unnecessary_await_in_return.dart b/test_data/rules/unnecessary_await_in_return.dart
index 9ef354e..7b117d1 100644
--- a/test_data/rules/unnecessary_await_in_return.dart
+++ b/test_data/rules/unnecessary_await_in_return.dart
@@ -60,6 +60,6 @@
 // https://github.com/dart-lang/linter/issues/1518
 class B {
   Future<num> foo() async => 1;
-  Future<int> bar() async => await foo(); // OK
+  Future<int> bar() async => await foo() as int; // OK
   Future<num> buzz() async => await bar(); // LINT
 }
diff --git a/test_data/rules/unnecessary_const.dart b/test_data/rules/unnecessary_const.dart
index df08d6a..30091e0 100644
--- a/test_data/rules/unnecessary_const.dart
+++ b/test_data/rules/unnecessary_const.dart
@@ -14,6 +14,9 @@
   const A(); // OK
   A(); // OK
 
+  A.new(); // OK
+  const A.new(); // OK
+
   const A([]); // OK
   A([]); // OK
   A(const []); // OK
@@ -33,4 +36,8 @@
   final v10 = const A([]); // OK
   final v11 = const A(const {}); // LINT
   final v12 = const A({}); // OK
+
+  const v13 = const A.new(); // LINT
+  final v14 = A.new(const []); // OK
+  final v15 = const A.new(const []); // LINT
 }
diff --git a/test_data/rules/unnecessary_constructor_name.dart b/test_data/rules/unnecessary_constructor_name.dart
new file mode 100644
index 0000000..3f4c4f2
--- /dev/null
+++ b/test_data/rules/unnecessary_constructor_name.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unnecessary_constructor_name`
+
+class A {
+  A.new(); // LINT
+  A.ok(); // OK
+}
+
+var a = A.new(); // LINT
+var aa = A(); // OK
+var aaa = A.ok(); // OK
+var makeA = A.new; // OK
diff --git a/test_data/rules/unnecessary_getters.dart b/test_data/rules/unnecessary_getters.dart
index c144e85..2efa76b 100644
--- a/test_data/rules/unnecessary_getters.dart
+++ b/test_data/rules/unnecessary_getters.dart
@@ -46,7 +46,7 @@
 }
 
 class Q {
-  int _q; //OK
+  int _q = 0; //OK
   int get q => _q;
 }
 
diff --git a/test_data/rules/unnecessary_getters_setters.dart b/test_data/rules/unnecessary_getters_setters.dart
index 346bade..6484a7e 100644
--- a/test_data/rules/unnecessary_getters_setters.dart
+++ b/test_data/rules/unnecessary_getters_setters.dart
@@ -9,7 +9,7 @@
 class Box {
   var _contents;
   get contents => _contents; //LINT [7:8]
-  set contents(value) //LINT
+  set contents(value)
   {
     _contents = value;
   }
@@ -21,7 +21,7 @@
   {
     return _contents;
   }
-  set contents(value) //LINT
+  set contents(value)
   {
     _contents = value;
   }
@@ -33,12 +33,12 @@
   {
     return _contents;
   }
-  set contents(value) => _contents = value; //LINT
+  set contents(value) => _contents = value;
 }
 
 class Box4 {
   var _contents;
-  get contents {
+  int get contents {
     return _contents;
   }
   set contents(int value) // OK -- notice the type
diff --git a/test_data/rules/unnecessary_lambdas.dart b/test_data/rules/unnecessary_lambdas.dart
index 994e604..870a600 100644
--- a/test_data/rules/unnecessary_lambdas.dart
+++ b/test_data/rules/unnecessary_lambdas.dart
@@ -9,6 +9,38 @@
 // ignore_for_file: prefer_expression_function_bodies
 // ignore_for_file: unused_local_variable
 
+class CC {
+  const CC();
+}
+
+var ccs = [ () => const CC()]; // OK
+
+typedef Maker = Object Function();
+
+class C {
+  int c;
+  C([this.c = 3, int y = 0]);
+}
+
+var l = <Maker>[
+  C.new, // OK
+  () => C(), // LINT
+  () => C(3), // OK
+];
+
+void constructorTearOffs() {
+  var listOfInts = [1, 2, 3];
+  for (var c in listOfInts.map((x) => C(x))) { // LINT
+    print(c.c);
+  }
+  for (var c in listOfInts.map((x) => C(3))) { // OK
+    print(c.c);
+  }
+  for (var c in listOfInts.map((x) => C(x, 3))) { // OK
+    print(c.c);
+  }
+}
+
 int count(String s) => s.length;
 final strings = ['a', 'bb', 'ccc', 'dddd'];
 
@@ -64,8 +96,8 @@
   final array = <MyClass>[];
   final x = MyClass();
 
-  final notRelevantQuestionPeriod = (p) => array[x?.m1].m2(p); // OK
-  final correctNotRelevantQuestionPeriod = array[x?.m1].m2; // OK
+  final notRelevantQuestionPeriod = (p) => array[x?.m1 ?? 0].m2(p); // OK
+  final correctNotRelevantQuestionPeriod = array[x?.m1 ?? 0].m2; // OK
 
   finalList.forEach((name) { // LINT
     print(name);
@@ -75,7 +107,7 @@
   // Lambdas as parameters.
   finalList.where((e) => finalList.contains(e)); // LINT
   finalList.where((e) => nonFinalList.contains(e)); // OK
-  finalList.where((e) => finalList?.contains(e)); // OK
+  finalList.where((e) => finalList?.contains(e) ?? false); // OK
   finalList.where(finalList.contains); // OK
 
   // Lambdas assigned to variables.
@@ -100,7 +132,7 @@
       ((a) => e.contains(a))(e)); // OK
 
   finalList.where((e) => // OK
-      ((a) => e?.contains(a))(e)); // OK
+      ((a) => e?.contains(a) ?? false)(e)); // OK
 
   var noStatementLambda = () { // OK
     // Empty lambda
@@ -125,7 +157,7 @@
   var a = names.where((e) => ((e) => e.contains(e))(e)); // LINT
   var b = names.where((e) => // LINT
       ((e) => e?.contains(e))(e));
-  names.where((e) => e?.contains(e)); // OK
+  names.where((e) => e?.contains(e) ?? false); // OK
 
   var c = names.where((e) { // LINT
     return ((e) {
diff --git a/test_data/rules/unnecessary_late.dart b/test_data/rules/unnecessary_late.dart
new file mode 100644
index 0000000..165bc2e
--- /dev/null
+++ b/test_data/rules/unnecessary_late.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unnecessary_late`
+
+late String unnecessaryTopLevelLate = ''; // LINT
+
+late String necessaryTopLevelLate; // OK
+
+late String unnecessaryListLateOne = '', // LINT
+    unnecessaryListLateTwo = ''; // LINT
+
+late String necessaryListLate, // OK
+    unnecessaryListLate = ''; // LINT
+
+String unnecessaryTopLevel = ''; // OK
+
+class Test {
+  static late String unnecessaryStaticLate = ''; // LINT
+
+  static late String necessaryStaticLate; // OK
+
+  static String unnecessaryStatic = ''; // OK
+
+  void test() {
+    late String necessaryLocal = ''; // OK
+  }
+}
diff --git a/test_data/rules/unnecessary_library_directive.dart b/test_data/rules/unnecessary_library_directive.dart
new file mode 100644
index 0000000..b954d42
--- /dev/null
+++ b/test_data/rules/unnecessary_library_directive.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unnecessary_library_directive`
+
+library not_needed; //LINT
diff --git a/test_data/rules/unnecessary_new.dart b/test_data/rules/unnecessary_new.dart
index 65104eb..f1df82a 100644
--- a/test_data/rules/unnecessary_new.dart
+++ b/test_data/rules/unnecessary_new.dart
@@ -14,6 +14,10 @@
   new A(); // LINT
   A(); // OK
 
+  // Constructor tear-offs.
+  new A.new(); // LINT
+  const A.new(); // OK
+
   new A.c1(); // LINT
   A.c1(); // OK
 
diff --git a/test_data/rules/unnecessary_null_aware_assignments.dart b/test_data/rules/unnecessary_null_aware_assignments.dart
index 128d9fd..02e2e82 100644
--- a/test_data/rules/unnecessary_null_aware_assignments.dart
+++ b/test_data/rules/unnecessary_null_aware_assignments.dart
@@ -11,3 +11,10 @@
     x ??= 1; //OK
   }
 }
+
+int? get x => null;
+set x(int? x) { }
+
+void f() {
+  x ??= null; //OK
+}
diff --git a/test_data/rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart b/test_data/rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart
new file mode 100644
index 0000000..67f08fd
--- /dev/null
+++ b/test_data/rules/unnecessary_null_aware_operator_on_extension_on_nullable.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unnecessary_null_aware_operator_on_extension_on_nullable`
+
+extension E on int? {
+  int get foo => 1;
+  void set foo(int v) {}
+  String operator [](int i) => '';
+  void operator []=(int i, String v) {}
+  int m() => 1;
+}
+
+f(int? i) {
+  i?.foo; // LINT
+  i.foo; // OK
+  E(i)?.foo; // LINT
+  E(i).foo; // OK
+
+  i?.foo = 1; // LINT
+  i.foo = 1; // OK
+  E(i)?.foo = 1; // LINT
+  E(i).foo = 1; // OK
+
+  i?[0]; // LINT
+  i[0]; // OK
+  E(i)?[0]; // LINT
+  E(i)[0]; // OK
+
+  i?[0] = ''; // LINT
+  i[0] = ''; // OK
+  E(i)?[0] = ''; // LINT
+  E(i)[0] = ''; // OK
+
+  i?.m(); // LINT
+  i.m(); // OK
+  E(i)?.m(); // LINT
+  E(i).m(); // OK
+}
diff --git a/test_data/rules/unnecessary_null_checks.dart b/test_data/rules/unnecessary_null_checks.dart
new file mode 100644
index 0000000..63c4dbd
--- /dev/null
+++ b/test_data/rules/unnecessary_null_checks.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unnecessary_null_checks`
+
+int? i;
+
+int? j1 = i!; // LINT
+int? j2 = (i!); // LINT
+
+m1a(int? p) => m1a(i!); // LINT
+m1b(int? p) => m1b((i!)); // LINT
+
+m2a({required String s, int? p}) => m2a(p: i!, s: ''); // LINT
+m2b({required String s, int? p}) => m2b(p: (i!), s: ('')); // LINT
+
+class A {
+  A([int? p]) {
+    A(i!); // LINT
+    A((i!)); // LINT
+  }
+
+  m1a(int? p) => m1a(i!); // LINT
+  m1b(int? p) => m1b((i!)); // LINT
+
+  m2a({required String s, int? p}) => m2a(p: i!, s: ''); // LINT
+  m2b({required String s, int? p}) => m2b(p: (i!), s: ('')); // LINT
+
+  m3a(int? p) => p!; // OK
+  m3b(int? p) {
+    return p!; // OK
+  }
+
+  operator +(int? p) => A() + i!; // LINT
+  operator -(int? p) => A() + (i!); // LINT
+}
+
+int? f1(int? i) => i!; // LINT
+int? f2(int? i) { return i!; } // LINT
+
+f3(int? i) {
+  int? v;
+  v = i!; // LINT
+}
+
+f4(int? p) {
+  int? v;
+  v ??= 1;
+  v += p!; // OK
+}
+
+autoPromote() {
+  int? v2;
+  v2 = v2!; // OK
+}
+
+f5(int? p) {
+  int? v1;
+  v1 ??= p!; // OK
+}
+
+Future<int?> f6(int? p) async => await p!; // LINT
+Future<int> f6ok(int? p) async => await p!; // OK
+List<int?> f7(int? p) => [p!]; // LINT
+List<int> f7ok(int? p) => [p!]; // OK
+Set<int?> f8(int? p) => {p!}; // LINT
+Set<int> f8ok(int? p) => {p!}; // OK
+Map<int?, String> f9(int? p) => {p!: ''}; // LINT
+Map<int, String> f9ok(int? p) => {p!: ''}; // OK
+Map<String, int?> f10(int? p) => {'': p!}; // LINT
+Map<String, int> f10ok(int? p) => {'': p!}; // OK
+Map<String, int?> f11_if(int? p) => {if (1 != 0) '': p!}; // LINT
+Map<String, int?> f11_if2(int? p) => {if (1 != 0) if (1 != 0) '': p!}; // LINT
+Map<String, int> f11_if_ok(int? p) => {if (1 != 0) '': p!}; // OK
+Map<String, int?> f11_for(int? p) => {for (var a in <int>[]) '': p!}; // LINT
+Map<String, int> f11_for_ok(int? p) => {for (var a in <int>[]) '': p!}; // OK
+Iterable<int?> f12(int? p) sync* {yield p!;} // LINT
+Iterable<int> f12ok(int? p) sync* {yield p!;} // OK
+Stream<int?> f13(int? p) async* {yield p!;} // LINT
+Stream<int> f13ok(int? p) async* {yield p!;} // OK
+Future<void> f14(int? p) async {
+  var f = Future(() => p);
+  int? i;
+  i = await f!; // LINT
+}
+Future<int?> f15(int? p) async => p!; // LINT
+Future<int> f15ok(int? p) async => p!; // OK
+dynamic f16(int? p) async => p!; // OK
+
+typedef F17 = Future<int?>;
+F17 f17(int? p) async => p!; // LINT
+typedef F17ok = Future<int>;
+F17ok f17ok(int? p) async => p!; // OK
diff --git a/test_data/rules/unnecessary_overrides.dart b/test_data/rules/unnecessary_overrides.dart
index dd356f0..68f1130 100644
--- a/test_data/rules/unnecessary_overrides.dart
+++ b/test_data/rules/unnecessary_overrides.dart
@@ -4,6 +4,8 @@
 
 // test w/ `dart test -N unnecessary_overrides`
 
+import 'package:meta/meta.dart';
+
 class _MyAnnotation {
   const _MyAnnotation();
 }
@@ -16,11 +18,12 @@
 
   int max1(int a, int b) => 0;
   int max2(int a, int b) => 0;
-  int m1({int a, int b}) => 0;
-  int m2({int a, int b}) => 0;
-  int m3({int a, int b}) => 0;
+  int m1({int a = 0, int b = 0}) => 0;
+  int m2({int a = 0, int b = 0}) => 0;
+  int m3({int a = 0, int b = 0}) => 0;
+  int m4({int a = 0, int b = 0}) => 0;
   int operator +(other) => 0;
-  Base operator ~()=> null;
+  Base operator ~() => Base();
   @override
   int get hashCode => 13;
 }
@@ -30,7 +33,8 @@
   int get x => super.x; // LINT
 
   @override
-  set x(other) { // LINT
+  set x(other)  // LINT
+  {
     super.x = other;
   }
 
@@ -41,19 +45,22 @@
   int max2(int a, int b) => super.max2(b, a); // OK
 
   @override
-  int m1({int a, int b}) => super.m1(a: a, b: b); // LINT
+  int m1({int a = 0, int b = 0}) => super.m1(a: a, b: b); // LINT
 
   @override
-  int m2({int a, int b}) => super.m2(b: b, a: a); // LINT
+  int m2({int a = 0, int b = 0}) => super.m2(b: b, a: a); // LINT
 
   @override
-  int m3({int a, int b}) => super.m3(b: a, a: b); // OK
+  int m3({int a = 0, int b = 0}) => super.m3(b: a, a: b); // OK
+
+  @override
+  int m4({int a = 0, int b = 0}) => super.m1(a: a, b: b); // OK
 
   @override
   int operator +(other) => super + other; // LINT
 
   @override
-  Base operator ~()=> ~super; // LINT
+  Base operator ~() => ~super; // LINT
 
   @override
   @myAnnotation
@@ -95,7 +102,8 @@
 
 class B extends A {
   @override
-  void foo() { // LINT
+  void foo() // LINT
+  {
     super.foo();
   }
 
@@ -108,7 +116,8 @@
   int getA(Iterable a) => super.getA(a); // LINT
 
   @override
-  int getB(Iterable a) { // LINT
+  int getB(Iterable a) // LINT
+  {
     return super.getB(a);
   }
 
@@ -120,40 +129,42 @@
 }
 
 class C {
-  num get g => null;
-  set s(int v) => null;
-  num m(int v) => null;
-  num m1({int v = 20}) => null;
-  num m2([int v = 20]) => null;
-  num operator +(int other) => null;
+  num get g => 0;
+  set s(int v) {}
+  @protected
+  num m(int v) => 0;
+  @protected
+  num m1({int v = 20}) => 0;
+  num m2([int v = 20]) => 0;
+  num operator +(int other) => 0;
 }
 class ReturnTypeChanged extends C {
   @override
-  int get g => super.g; // OK
+  int get g => super.g as int; // OK
   @override
-  int m(int v) => super.m(v); // OK
+  int m(int v) => super.m(v) as int; // OK
   @override
-  int m1({int v = 20}) => super.m1(v: v); // OK
+  int m1({int v = 20}) => super.m1(v: v) as int; // OK
   @override
-  int m2([int v = 20]) => super.m2(v); // OK
+  int m2([int v = 20]) => super.m2(v) as int; // OK
   @override
-  int operator +(int other) => super + other; // OK
+  int operator +(int other) => super + other as int; // OK
 }
 class ParameterTypeChanged extends C {
   @override
-  set s(num v) => super.s = v; // OK
+  set s(num v) => super.s = v as int; // OK
   @override
-  num m(num v) => super.m(v); // OK
+  num m(num v) => super.m(v as int); // OK
   @override
-  num m1({num v = 20}) => super.m1(v: v); // OK
+  num m1({num v = 20}) => super.m1(v: v as int); // OK
   @override
-  num m2([num v = 20]) => super.m2(v); // OK
+  num m2([num v = 20]) => super.m2(v as int); // OK
   @override
-  num operator +(num other) => super + other; // OK
+  num operator +(num other) => super.g + other; // OK
 }
 class ParameterNameChanged extends C {
   @override
-  set s(num v2) => super.s = v2; // OK
+  set s(num v2) => super.s = v2 as int; // OK
   @override
   num m(int v2) => super.m(v2); // OK
   @override
@@ -175,11 +186,11 @@
 }
 class ParameterAdditional extends C {
   @override
-  num m(int v, [int v2]) => super.m(v); // OK
+  num m(int v, [int v2 = 1]) => super.m(v); // OK
   @override
-  num m1({int v = 20, int v2}) => super.m1(v: v); // OK
+  num m1({int v = 20, int? v2}) => super.m1(v: v); // OK
   @override
-  num m2([int v = 20, int v2]) => super.m2(v); // OK
+  num m2([int v = 20, int? v2]) => super.m2(v); // OK
 }
 class ParameterDefaultChange extends C {
   @override
@@ -187,6 +198,12 @@
   @override
   num m2([int v = 10]) => super.m2(v); // OK
 }
+class ProtectedMadePublic extends C {
+  @override
+  num m(int v) => super.m(v); // OK
+  @protected
+  num m1({int v = 20}) => super.m1(v: v); // LINT
+}
 
 // noSuchMethod is allowed to proxify
 class D implements C {
@@ -199,3 +216,31 @@
   @override
   num get g => super.g; // OK
 }
+
+class F<T> {
+  T m1() => throw 42;
+
+  T get g1 => throw 42;
+
+  void set s1(T value) => throw 42;
+
+  T operator +(T other) => throw 42;
+}
+
+class G extends F<int> {
+  @override
+  int m1() => super.m1(); // LINT
+
+  @override
+  int get g1 => super.g1; // LINT
+
+  @override
+  void set s1(int value) => super.s1 = value; // LINT
+
+  @override
+  int operator +(int other) => super + other; // LINT
+}
+
+extension on int {
+  int m1() => 7;
+}
diff --git a/test_data/rules/unnecessary_parenthesis.dart b/test_data/rules/unnecessary_parenthesis.dart
index 310078c..47d16f5 100644
--- a/test_data/rules/unnecessary_parenthesis.dart
+++ b/test_data/rules/unnecessary_parenthesis.dart
@@ -6,6 +6,44 @@
 
 import 'dart:async';
 
+/// https://github.com/dart-lang/linter/issues/2944
+void foo() {
+  final items = [];
+  (() => [].add('something')).compose(() {}); // OK
+  (() => '').hashCode; // OK
+  (() => '').f = 1; // OK
+  (() => '') + 1; // OK
+  (() => '')[0]; // OK
+}
+
+extension A on void Function() {
+  void Function() compose(void Function() other) => () {
+        this();
+        other();
+      };
+
+  set f(int f) {}
+  operator +(int x) {}
+  int operator [](int i) => 0;
+}
+
+var func = (() => null); // LINT
+
+class D {
+  D.d([int? x, int? y]);
+}
+
+/// https://github.com/dart-lang/linter/issues/2907
+void constructorTearOffs() {
+  var makeD = D.d;
+  (makeD)(1); // LINT
+  (D.d)(1); // LINT
+  (List<int>.filled)(3, 0); // LINT
+  (List.filled)<int>(3, 0); // OK
+  var tearoff = (List<int>.filled); // LINT
+  (List<int>).toString(); //OK
+}
+
 var a, b, c, d;
 
 main() async {
@@ -19,6 +57,8 @@
   (await new Future.value(1)).toString(); // OK
   ('' as String).toString(); // OK
   !(true as bool); // OK
+  (b - a) as num; // OK
+  (b - a) is num; // OK
   a = (a); // LINT
   (a) ? true : false; // LINT
   true ? (a) : false; // LINT
@@ -42,24 +82,30 @@
   // OK because unary operators mixed with space-separated tokens may have
   // unexpected ordering.
   !(const [7].contains(42)); // OK
-  !(new List(3).contains(42)); // OK
+  !(new List.empty().contains(42)); // OK
   !(await Future.value(false)); // OK
-  -(new List(3).length); // OK
-  !(new List(3).length.isEven); // OK
-  -(new List(3).length.abs().abs().abs()); // OK
-  -(new List(3).length.sign.sign.sign); // OK
+  -(new List.empty().length); // OK
+  !(new List.empty().length.isEven); // OK
+  -(new List.empty().length.abs().abs().abs()); // OK
+  -(new List.empty().length.sign.sign.sign); // OK
   !(const [7]).contains(42); // OK
 
   // OK because some methods are defined on Type, but removing the parentheses
   // would attempt to call a _static_ method on the target.
   (String).hashCode;
   (int).runtimeType;
-  (bool).noSuchMethod();
+  (bool).noSuchMethod(invocation()!);
   (double).toString();
 
   ({false: 'false', true: 'true'}).forEach((k, v) => print('$k: $v'));
   ({false, true}).forEach(print);
   ({false, true}).length;
+  ({false, true}).length.toString();
+  ({1, 2, 3}) + {4};
+  ({1, 2, 3}).cast<num>;
+  /* comment */ ({1, 2, 3}).length;
+  // comment
+  ({1, 2, 3}).length;
   print(({1, 2, 3}).length); // LINT
   ([false, true]).forEach(print); // LINT
   (0.sign).isEven; // LINT
@@ -72,17 +118,21 @@
 
 }
 
+Invocation? invocation() => null;
+
 m({p}) => null;
 
 bool Function(dynamic) get fn => (x) => x is bool ? x : false;
 
 class ClassWithFunction {
-  Function f;
-  int number;
+  Function? f;
+  int? number;
 
+  ClassWithFunction();
   ClassWithFunction.named(int a) : this.number = (a + 2); // LINT
   // https://github.com/dart-lang/linter/issues/1473
-  ClassWithFunction.named2(Function value) : this.f = (value ?? (_) => 42); // OK
+  ClassWithFunction.named2(Function value)
+      : this.f = (value ?? (_) => 42); // OK
 }
 
 class ClassWithClassWithFunction {
@@ -97,7 +147,11 @@
 
   UnnecessaryParenthesis()
       : c = (ClassWithClassWithFunction()
-          ..c = ClassWithFunction().f = () => 42); // OK
+          ..c = (ClassWithFunction()..f = () => 42)); // OK
+}
+
+extension<T> on Set<T> {
+  Set<T> operator +(Set<T> other) => {...this, ...other};
 }
 
 class MyType extends Type {
diff --git a/test_data/rules/unnecessary_statements.dart b/test_data/rules/unnecessary_statements.dart
index 6f2a014..1570466 100644
--- a/test_data/rules/unnecessary_statements.dart
+++ b/test_data/rules/unnecessary_statements.dart
@@ -4,10 +4,19 @@
 
 // test w/ `dart test -N unnecessary_statements`
 
+void constructorTearOffs() {
+  MyClass.new; // LINT
+  MyClass.named; // LINT
+
+  var m = MyClass.new;
+  m().foo; // LINT
+  m().field; // LINT
+}
+
 String f(Object o) {
   // See: https://github.com/dart-lang/linter/issues/2163
   o as int; // OK
-  return null;
+  return '';
 }
 
 notReturned() {
@@ -169,11 +178,14 @@
 bool someBool = true;
 bool foo() => true;
 get getter => true;
-int field;
+int field = 0;
 
 class MyClass {
-  int field;
+  int field = 0;
   bool foo() => true;
 
   get getter => true;
+
+  MyClass();
+  MyClass.named();
 }
diff --git a/test_data/rules/unnecessary_string_interpolations.dart b/test_data/rules/unnecessary_string_interpolations.dart
index a33cd01..a6871d7 100644
--- a/test_data/rules/unnecessary_string_interpolations.dart
+++ b/test_data/rules/unnecessary_string_interpolations.dart
@@ -5,12 +5,12 @@
 // test w/ `dart test -N unnecessary_string_interpolations`
 
 class Node {
-  final String? text;
+  final String? text = '';
   @override
   String toString() => '$text'; // OK
 }
 
-String o;
+String o = '';
 
 f() {
   o = '$o'; // LINT
diff --git a/test_data/rules/unnecessary_this.dart b/test_data/rules/unnecessary_this.dart
index a26ece5..50dd7de 100644
--- a/test_data/rules/unnecessary_this.dart
+++ b/test_data/rules/unnecessary_this.dart
@@ -5,16 +5,16 @@
 // test w/ `dart test -N unnecessary_this`
 
 extension E on int {
-  String f() => this?.toString(); //OK
-  int get h => this?.hashCode; //OK
+  String? f() => this?.toString(); //OK
+  int? get h => this?.hashCode; //OK
 }
 
 void bar() {}
 
-int x;
+int x = 0;
 
 class Parent {
-  int x, y;
+  int x = 0, y = 0;
   void bar() {}
   void foo() {}
 }
@@ -40,7 +40,7 @@
 void duplicate() {}
 
 class A {
-  num x, y;
+  num x = 0, y = 0;
 
   A(num x, num y) {
     this.x = x; // OK
diff --git a/test_data/rules/unnecessary_to_list_in_spreads.dart b/test_data/rules/unnecessary_to_list_in_spreads.dart
new file mode 100644
index 0000000..aaf21c0
--- /dev/null
+++ b/test_data/rules/unnecessary_to_list_in_spreads.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unnecessary_to_list_in_spreads`
+
+var ok = [
+  ...[1, 2].whereType<int>(), // OK
+];
+
+var t1 = [
+  ...[1, 2].toList(), // LINT
+];
+var t2 = [
+  ...{1, 2}.toList(), // LINT
+];
+var t3 = [
+  ...?[1, 2].toList(), // LINT
+];
+var t4 = [
+  ...?{1, 2}.toList(), // LINT
+];
+var t5 = [
+  ...[1, 2].whereType<int>().toList(), // LINT
+];
diff --git a/test_data/rules/unreachable_from_main.dart b/test_data/rules/unreachable_from_main.dart
new file mode 100644
index 0000000..94ff147
--- /dev/null
+++ b/test_data/rules/unreachable_from_main.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N unreachable_from_main`
+
+import 'package:meta/meta.dart';
+
+/// see [Comment]
+main() // OK
+{
+  _f5();
+  f1();
+  f3(() {
+    f4(b);
+  });
+  f4(b);
+  usageInTypeBound();
+  usageInFunctionType();
+  usageInDefaultValue();
+  usageInAnnotation();
+  Future<C5>.value(C5()).extensionUsage();
+  accessors();
+  print(c2);
+}
+
+class Comment {} // OK
+
+const a = 1; // LINT
+const b = 1; // OK
+
+final int //
+    c1 = 1, // LINT
+    c2 = 2; // OK
+
+int v = 1; // LINT
+
+typedef A = String; // LINT
+
+class C {} // LINT
+
+mixin M {} // LINT
+
+enum E { e } // LINT
+
+void f() {} // LINT
+
+@visibleForTesting
+void forTest() {} // OK
+
+void f1() // OK
+{
+  f2();
+}
+
+void f2() // OK
+{
+  f1();
+}
+
+void f3(Function f) {} // OK
+void f4(int p) {} // OK
+
+int id = 0; // OK
+void _f5() {
+  id++;
+}
+
+@pragma('vm:entry-point')
+void f6() {} // OK
+
+const entryPoint = pragma('vm:entry-point');
+@entryPoint
+void f7() {} // OK
+
+@pragma('other')
+void f8() {} // LINT
+
+// test accessors
+int get id9 => 0;
+void set id9(int value) {}
+void accessors() {
+  id9 += 4; // usage
+}
+
+// Usage in type bound
+class C1 {}
+
+void usageInTypeBound<T extends C1>() {}
+
+// Usage in Function type
+class C2 {}
+
+void Function(C2)? usageInFunctionType() => null;
+
+// Usage in default value
+class C3 {
+  const C3();
+}
+
+void usageInDefaultValue([Object? p = const C3()]) {}
+
+// Usage in annotation
+class C4 {
+  const C4();
+}
+
+@C4()
+void usageInAnnotation() {}
+
+// Usage in type parameter in extension `on` clause.
+class C5 {}
+
+extension UsedPublicExt on Future<C5> {
+  extensionUsage() {}
+}
+
+// Usage in type parameter in extension `on` clause.
+class C6 {} //LINT
+
+extension UnusedPublicExt on C6 //LINT
+{
+  m() {}
+}
+
+class C7 // LINT
+{
+  C7();
+  C7.named();
+}
diff --git a/test_data/rules/unrelated_type_equality_checks.dart b/test_data/rules/unrelated_type_equality_checks.dart
index 723f748..3d664a5 100644
--- a/test_data/rules/unrelated_type_equality_checks.dart
+++ b/test_data/rules/unrelated_type_equality_checks.dart
@@ -34,7 +34,7 @@
 }
 
 void someFunction5(Object object) {
-  List<ClassBase> someList;
+  List<ClassBase> someList = [];
 
   for (ClassBase someInstance in someList) {
     if (object == someInstance) print('someFunction5'); // OK
@@ -42,7 +42,7 @@
 }
 
 void someFunction6(Object object) {
-  List someList;
+  List someList = [];
 
   for (var someInstance in someList) {
     if (object == someInstance) print('someFunction6'); // OK
@@ -50,13 +50,13 @@
 }
 
 void someFunction7() {
-  List someList;
+  List someList = [];
 
   if (someList.length == 0) print('someFunction7'); // OK
 }
 
 void someFunction8(ClassBase instance) {
-  DerivedClass1 other;
+  DerivedClass1 other = DerivedClass1();
 
   if (other == instance) print('someFunction8'); // OK
 }
@@ -160,7 +160,7 @@
 
 void function23() {
   Future getFuture() => new Future.value();
-  Future<void> aVoidFuture;
+  Future<void>? aVoidFuture;
 
   var aFuture = getFuture();
   if (aFuture == aVoidFuture) print('someFunction23'); // OK
@@ -174,7 +174,7 @@
   if (int == double) print('someFunction30'); // OK
   ClassWCall callable = ClassWCall();
   if (c.determinant == callable) print('someFunction30'); // OK
-  SubClassWCall callable2 = SubClassWCall2();
+  SubClassWCall callable2 = SubClassWCall();
   if (c.determinant == callable2) print('someFunction30'); // OK
 }
 
diff --git a/test_data/rules/unsafe_html.dart b/test_data/rules/unsafe_html.dart
index 6c02f3d..b94dacb 100644
--- a/test_data/rules/unsafe_html.dart
+++ b/test_data/rules/unsafe_html.dart
@@ -11,7 +11,6 @@
   var embed = EmbedElement();
   embed.src = 'foo'; // LINT
   IFrameElement()..src = 'foo'; // LINT
-  ImageElement()..src = 'foo'; // LINT
 
   var script = ScriptElement();
   script.src = 'foo.js'; // LINT
@@ -61,9 +60,9 @@
 }
 
 class C {
-  String src;
-  String srcdoc;
-  String href;
+  String src = '';
+  String srcdoc = '';
+  String href = '';
 
   C();
 
diff --git a/test_data/rules/use_build_context_synchronously.dart b/test_data/rules/use_build_context_synchronously.dart
index 8e93629..745a674 100644
--- a/test_data/rules/use_build_context_synchronously.dart
+++ b/test_data/rules/use_build_context_synchronously.dart
@@ -7,6 +7,19 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
+void mountedBinOpAnd(BuildContext context, bool condition) async {
+  await Future<void>.delayed(Duration());
+  if (condition && context.mounted) {
+    await Navigator.of(context).pushNamed('routeName'); // OK
+  }
+}
+
+void mountedBinOpAOr(BuildContext context, bool condition) async {
+  await Future<void>.delayed(Duration());
+  if (condition || !mounted) return;
+  await Navigator.of(context).pushNamed('routeName'); // OK
+}
+
 void awaitInSwitchCase(BuildContext context) async {
   await Future<void>.delayed(Duration());
   switch (1) {
@@ -290,7 +303,7 @@
   }
 
   @override
-  Widget build(BuildContext context) => const Placeholder();
+  Widget build(BuildContext context) => Placeholder();
 }
 
 void topLevel(BuildContext context) async {
@@ -390,3 +403,22 @@
     f(context); // TODO: LINT
   });
 }
+
+// https://github.com/dart-lang/linter/issues/3457
+void awaitInIf1(BuildContext context, Future<bool> condition) async {
+  if (await condition) {
+    Navigator.of(context).pushNamed('routeName'); // LINT
+  }
+}
+
+void awaitInIf2(BuildContext context, Future<bool> condition) async {
+  var b = await condition;
+  if (b) {
+    Navigator.of(context).pushNamed('routeName'); // LINT
+  }
+}
+
+void awaitInIf3(BuildContext context, Future<bool> condition) async {
+  if (await condition) return;
+  Navigator.of(context).pushNamed('routeName'); // LINT
+}
diff --git a/test_data/rules/use_colored_box.dart b/test_data/rules/use_colored_box.dart
new file mode 100644
index 0000000..674d16a
--- /dev/null
+++ b/test_data/rules/use_colored_box.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N use_colored_box`
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+class Color {
+  Color(int value);
+}
+
+Widget containerWithoutArguments() {
+  return Container(); // OK
+}
+
+Widget containerWithKey() {
+  return Container( // OK
+    key: Key('abc'),
+  );
+}
+
+Widget containerWithColor() {
+  return Container( // OK
+    color: Color(0xffffffff),
+  );
+}
+
+Widget containerWithChild() {
+  return Container( // OK
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithKeyAndChild() {
+  return Container( // OK
+    key: Key('abc'),
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithKeyAndColor() {
+  return Container( // OK
+    key: Key('abc'),
+    color: Color(0xffffffff),
+  );
+}
+
+Widget containerWithColorAndChild() {
+  return Container( // LINT
+    color: Color(0xffffffff),
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithKeyAndColorAndChild() {
+  return Container( // LINT
+    key: Key('abc'),
+    color: Color(0xffffffff),
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithAnotherArgument() {
+  return Container( // OK
+    width: 20,
+  );
+}
+
+Widget containerWithColorAndAdditionalArgument() {
+  return Container( // OK
+    color: Color(0xffffffff),
+    width: 20,
+  );
+}
+
+Widget containerWithColorAndAdditionalArgumentAndChild() {
+  return Container( // OK
+    color: Color(0xffffffff),
+    width: 20,
+    child: SizedBox(),
+  );
+}
+
+Widget nullableColor(Color? myColor) {
+  return Container( // OK
+    color: myColor
+  );
+}
diff --git a/test_data/rules/use_decorated_box.dart b/test_data/rules/use_decorated_box.dart
new file mode 100644
index 0000000..1f43f2d
--- /dev/null
+++ b/test_data/rules/use_decorated_box.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `dart test -N use_decorated_box`
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter/painting.dart';
+
+Widget containerWithoutArguments() {
+  return Container(); // OK
+}
+
+Widget containerWithKey() {
+  return Container( // OK
+    key: Key('abc'),
+  );
+}
+
+Widget containerWithDecoration() {
+  return Container( // OK
+    decoration: BoxDecoration(),
+  );
+}
+
+Widget containerWithChild() {
+  return Container( // OK
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithKeyAndChild() {
+  return Container( // OK
+    key: Key('abc'),
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithKeyAndDecoration() {
+  return Container( // OK
+    key: Key('abc'),
+    decoration: BoxDecoration(),
+  );
+}
+
+Widget containerWithDecorationAndChild() {
+  return Container( // LINT
+    decoration: BoxDecoration(),
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithKeyAndDecorationAndChild() {
+  return Container( // LINT
+    key: Key('abc'),
+    decoration: BoxDecoration(),
+    child: SizedBox(),
+  );
+}
+
+Widget containerWithAnotherArgument() {
+  return Container( // OK
+    width: 20,
+  );
+}
+
+Widget containerWithDecorationAndAdditionalArgument() {
+  return Container( // OK
+    decoration: BoxDecoration(),
+    width: 20,
+  );
+}
+
+Widget containerWithDecorationAndAdditionalArgumentAndChild() {
+  return Container( // OK
+    decoration: BoxDecoration(),
+    width: 20,
+    child: SizedBox(),
+  );
+}
diff --git a/test_data/rules/experiments/nnbd/rules/use_if_null_to_convert_nulls_to_bools.dart b/test_data/rules/use_if_null_to_convert_nulls_to_bools.dart
similarity index 100%
rename from test_data/rules/experiments/nnbd/rules/use_if_null_to_convert_nulls_to_bools.dart
rename to test_data/rules/use_if_null_to_convert_nulls_to_bools.dart
diff --git a/test_data/rules/use_is_even_rather_than_modulo.dart b/test_data/rules/use_is_even_rather_than_modulo.dart
index 9603a67..37a59f2 100644
--- a/test_data/rules/use_is_even_rather_than_modulo.dart
+++ b/test_data/rules/use_is_even_rather_than_modulo.dart
@@ -4,26 +4,27 @@
 
 // test w/ `dart test -N use_is_even_rather_than_modulo`
 
-bool isEven = 1 % 2 == 0; //LINT
-bool isOdd = 13 % 2 == 1; //LINT
-int number = 3;
-bool c = number % 2 == 0; //LINT
+// ignore_for_file: unused_local_variable
 
-// Not equality operator is okay
-bool a = 1 % 2 >= 0;
-bool d = number % 2 != 0;
+void f() {
+  bool isEven = 1 % 2 == 0; //LINT
+  bool isOdd = 13 % 2 == 1; //LINT
+  int number = 3;
+  bool c = number % 2 == 0; //LINT
 
-// Modulo by any other number than 2 is okay
-d = number % 3 == 1;
+  // Not equality operator is okay
+  bool a = 1 % 2 >= 0;
+  bool d = number % 2 != 0;
 
-// Not modulo operation is okay.
-d = number + 2 == 0;
+  // Modulo by any other number than 2 is okay
+  d = number % 3 == 1;
 
-// Compare to not an IntegerLiteral is okay.
-bool b = 1 % 2 == 3-3;
-// Unknown operand type is okay.
-Class tmp;
-a = tmp % 2 == 0;
+  // Not modulo operation is okay.
+  d = number + 2 == 0;
 
-a = 1.isEven;
-a = 2.isOdd;
+  // Compare to not an IntegerLiteral is okay.
+  bool b = 1 % 2 == 3 - 3;
+
+  a = 1.isEven;
+  a = 2.isOdd;
+}
\ No newline at end of file
diff --git a/test_data/rules/use_key_in_widget_constructors.dart b/test_data/rules/use_key_in_widget_constructors.dart
index b8bcee6..6de5e8c 100644
--- a/test_data/rules/use_key_in_widget_constructors.dart
+++ b/test_data/rules/use_key_in_widget_constructors.dart
@@ -15,10 +15,15 @@
 
 class MyWidget extends StatelessWidget {
   MyWidget(); // LINT
-  MyWidget.withKey({Key key}) : super(key: key); // OK
-  MyWidget.withUnusedKey({Key key}); // LINT
-  factory MyWidget.fact() => null; // OK
+  MyWidget.withKey({Key? key}) : super(key: key ?? Key('')); // OK
+  MyWidget.withUnusedKey({Key? key}); // LINT
+  factory MyWidget.fact() => MyWidget(); // OK
   MyWidget._private(); // OK
   MyWidget.redirect() : this.withKey(key: Key('')); // OK
   MyWidget.superCall() : super(key: Key('')); // OK
 }
+
+class ConstWidget extends StatelessWidget {
+  const ConstWidget(); // LINT [9:11]
+  const ConstWidget.named(); // LINT [21:5]
+}
diff --git a/test_data/rules/use_late_for_private_fields_and_variables.dart b/test_data/rules/use_late_for_private_fields_and_variables.dart
deleted file mode 100644
index 9b7dd6b..0000000
--- a/test_data/rules/use_late_for_private_fields_and_variables.dart
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// test w/ `dart test -N use_late_for_private_fields_and_variables`
-
-int? i; // OK
-
-int? _i1; // LINT
-
-int? _i2; // LINT
-m2() {
-  _i2 = 1;
-}
-
-int? _i3; // OK
-m3() {
-  _i3 = null;
-}
-
-int? _i4; // LINT
-m4() {
-  _i4!.abs();
-}
-
-int? _i5; // OK
-m5() {
-  _i5?.abs();
-}
-
-int? _i6; // OK
-m6() {
-  if (_i6 != null) _i6.toString();
-}
-
-int? _i7; // OK
-m7(int? i) {
-  m7(_i7);
-}
-
-int? _i8; // LINT
-m8(int i) {
-  m8(_i8!);
-}
-
-int? _i9; // OK
-m9() {
-  _i9 == 1;
-}
-
-class A1 {
-  int? i; // OK
-  int? _i; // LINT
-}
-
-class _A2 {
-  int? i; // OK until we detect that _A2 is not returned anywhere
-  int? _i; // LINT
-}
-
-extension E1 on A1 {
-  static int? i1; // OK
-  static int? _i1; // LINT
-}
-
-extension _E2 on A1 {
-  static int? i1; // LINT
-}
-
-extension on A1 {
-  static int? i1; // LINT
-}
-
-class ContentChannelException {
-  const ContentChannelException._(this.value, [this._exception]);
-
-  final String value;
-
-  final Exception? _exception; // OK (const constructor)
-
-  Exception get exception => _exception!;
-}
diff --git a/test_data/rules/use_rethrow_when_possible.dart b/test_data/rules/use_rethrow_when_possible.dart
index 637d783..ece4c01 100644
--- a/test_data/rules/use_rethrow_when_possible.dart
+++ b/test_data/rules/use_rethrow_when_possible.dart
@@ -36,3 +36,13 @@
     }
   }
 }
+
+void bug2789() {
+  try {} catch (e) {
+    e != null ? e.toString() : throw e; // OK
+  }
+
+  try {} catch (e) {
+    print(throw e); // OK
+  }
+}
diff --git a/test_data/rules/use_setters_to_change_properties.dart b/test_data/rules/use_setters_to_change_properties.dart
index c8ff257..c2e1695 100644
--- a/test_data/rules/use_setters_to_change_properties.dart
+++ b/test_data/rules/use_setters_to_change_properties.dart
@@ -4,9 +4,11 @@
 
 // test w/ `dart test -N use_setters_to_change_properties`
 
+// ignore_for_file: unused_field
+
 abstract class A {
-  int _w;
-  int _x;
+  int _w = 0;
+  int _x = 0;
 
   void setW(int w) => _w = w; // LINT
 
@@ -26,7 +28,7 @@
 }
 
 class B extends A {
-  int _y;
+  int _y = 0;
 
   void setY(int y) { // OK because it is an inherited method.
     this._y = y;
@@ -38,8 +40,8 @@
 }
 
 class D implements C {
-  int _y;
-  int dd;
+  int _y = 0;
+  int dd = 0;
 
   void setY(int y) { // OK because it is an implementation method.
     this._y = y;
diff --git a/test_data/rules/use_string_buffers.dart b/test_data/rules/use_string_buffers.dart
index 1073bfb..0a30b6e 100644
--- a/test_data/rules/use_string_buffers.dart
+++ b/test_data/rules/use_string_buffers.dart
@@ -4,8 +4,10 @@
 
 // test w/ `dart test -N use_string_buffer`
 
+// ignore_for_file: unused_local_variable
+
 class A {
-  String buffer;
+  String buffer = '';
 
   void foo(int n) {
     int aux = n;
@@ -93,7 +95,7 @@
 
 void bar5() {
   for (final s in [ 'a', 'b']) {
-    String str;
+    String str = '';
     str += s; // OK
   }
 }
diff --git a/test_data/rules/use_to_and_as_if_applicable.dart b/test_data/rules/use_to_and_as_if_applicable.dart
index 223a264..fcc768a 100644
--- a/test_data/rules/use_to_and_as_if_applicable.dart
+++ b/test_data/rules/use_to_and_as_if_applicable.dart
@@ -5,7 +5,7 @@
 // test w/ `dart test -N use_to_and_as_if_applicable`
 
 class A {
-  A.from(B);
+  A.from(B _);
 }
 
 // Testing the regexp
@@ -72,3 +72,19 @@
     _functionWihParameter(0);
   }
 }
+
+abstract class C {
+  A getA();
+}
+
+class D1 extends C {
+  A getA() => A.from(B()); // OK
+}
+
+class D2 implements C {
+  A getA() => A.from(B()); // OK
+}
+
+mixin D3 on C {
+  A getA() => A.from(B()); // OK
+}
diff --git a/test_data/rules/void_checks.dart b/test_data/rules/void_checks.dart
index 9b31226..ea2e046 100644
--- a/test_data/rules/void_checks.dart
+++ b/test_data/rules/void_checks.dart
@@ -6,8 +6,40 @@
 
 // @dart=2.9
 
+// ignore_for_file: unused_local_variable
+
 import 'dart:async';
 
+Never fail() { throw 'nope'; }
+void foo(FutureOr<void> Function() f2) {}
+
+/// https://github.com/dart-lang/linter/issues/2685
+void m3() {
+  foo(() {
+    fail(); // OK
+  });
+
+  var f = Future.value(0);
+  f.then<void>((_) {
+    fail(); // OK
+  });
+}
+
+void m2() {
+  // https://github.com/dart-lang/linter/issues/2794
+  Future<void> f = Future.value(1).then<void>((_) { // OK
+    throw 'sad';
+  });
+}
+
+void foo2(FutureOr<void> x) {}
+
+void main() {
+  FutureOr<void> x;
+  // https://github.com/dart-lang/linter/issues/1675
+  foo(x); // OK
+}
+
 var x;
 get g => null;
 set s(v) {}
@@ -161,6 +193,7 @@
   void Function() returnsVoidFn() {
     return nonVoidFn; // OK
   }
+  returnsVoidFn();
 }
 
 allow_functionWithReturnType_forFunctionWithout_asComplexExpr() {
@@ -174,6 +207,7 @@
   void Function() returnsVoidFn() {
     return listNonVoidFn[0]; // OK
   }
+  returnsVoidFn();
 }
 
 allow_Null_for_void() {
@@ -205,11 +239,6 @@
   f = () => i++; // OK
 }
 
-missing_parameter_for_argument() {
-  void foo() {}
-  foo(0);
-}
-
 void emptyFunctionExpressionReturningFutureOrVoid(FutureOr<void> Function() f) {
   f = () {}; // OK
 }
diff --git a/tool/baseline/pana.json b/tool/baseline/pana.json
index 9e85165..8eb0e01 100644
--- a/tool/baseline/pana.json
+++ b/tool/baseline/pana.json
@@ -1,5 +1,5 @@
 {
   "scores": {
-    "grantedPoints": 70
+    "grantedPoints": 130
   }
 }
diff --git a/tool/benchmark.dart b/tool/benchmark.dart
index 5cd1526..ac63879 100644
--- a/tool/benchmark.dart
+++ b/tool/benchmark.dart
@@ -39,7 +39,7 @@
 }
 
 class BaseBenchmark extends BenchmarkBase {
-  const BaseBenchmark(String name) : super(name);
+  const BaseBenchmark(super.name);
 
   @override
   void exercise() {
@@ -66,10 +66,12 @@
 
   @override
   void run() {
-    // ignore: prefer_foreach
-    for (var name in ['foo.dart', 'a-b.dart', 'a-b.css.dart', 'foo']) {
-      hasOneDot(name);
-    }
+    [
+      'foo.dart',
+      'a-b.dart',
+      'a-b.css.dart',
+      'foo',
+    ].forEach(hasOneDot);
   }
 }
 
diff --git a/tool/bot.sh b/tool/bot.sh
index 2c7129e..9d4e4a4 100755
--- a/tool/bot.sh
+++ b/tool/bot.sh
@@ -58,7 +58,7 @@
 
   status=$?
 
-  pub global activate coverage
+  dart pub global activate coverage
 
   echo "Collecting coverage on port $OBS_PORT..."
 
@@ -74,7 +74,6 @@
     --lcov \
     --in=var/coverage.json \
     --out=var/lcov.info \
-    --packages=.packages \
     --report-on=lib \
     --check-ignore
 
@@ -86,9 +85,13 @@
   # Verify that the libraries are error free.
   dart analyze --fatal-infos .
 
+  # Enforce some linter-specific checks.
+  dart tool/checks/driver.dart
+
   echo ""
 
   # Run tests.
-  dart --disable-analytics \
+  dart --enable-asserts \
+    --disable-analytics \
     test/all.dart
 fi
diff --git a/tool/canonical/gh_labels.dart b/tool/canonical/gh_labels.dart
index dc0d2df..d4fea18 100644
--- a/tool/canonical/gh_labels.dart
+++ b/tool/canonical/gh_labels.dart
@@ -13,7 +13,7 @@
 /// Outputs a list of issues whose GH issue tags need updating.
 Future<void> main(List<String> args) async {
   var parser = ArgParser()
-    ..addOption('token', abbr: 't', help: 'Specifies a github auth token.');
+    ..addOption('token', abbr: 't', help: 'Specifies a GitHub auth token.');
   ArgResults options;
   try {
     options = parser.parse(args);
@@ -41,8 +41,8 @@
       if (title.contains(rule.key)) {
         var sets = rule.value;
         for (var set in sets) {
-          if (!issue.labels.any((label) => label.name.startsWith('set: '))) {
-            print('${issue.htmlUrl} => set: $set');
+          if (!issue.labels.any((label) => label.name.startsWith('set-'))) {
+            print('${issue.htmlUrl} => set-$set');
           }
         }
       }
diff --git a/tool/canonical/readme.md b/tool/canonical/readme.md
index 193ba58..de990dd 100644
--- a/tool/canonical/readme.md
+++ b/tool/canonical/readme.md
@@ -1,9 +1,9 @@
 Tools related to Dart and Flutter lints.
 
-* `gh_labels` - emits a list of open github issues that reference
-  lints in lint sets but are missing `set: ...` issue labels.
+* `gh_labels` - lists open GitHub issues that reference lint rules in 
+   lint sets but are missing `set- ...` issue labels.
 
 See: 
 
 * https://github.com/dart-lang/lints
-* https://github.com/flutter/packages/tree/master/packages/flutter_lints
\ No newline at end of file
+* https://github.com/flutter/packages/tree/main/packages/flutter_lints
diff --git a/tool/canonical/scorecard.dart b/tool/canonical/scorecard.dart
index bf5a62a..4603074 100644
--- a/tool/canonical/scorecard.dart
+++ b/tool/canonical/scorecard.dart
@@ -187,7 +187,7 @@
   bool get inFlutter => ruleSets.contains('flutter');
   bool get inRecommended => ruleSets.contains('recommended');
 
-  String get _ruleSets => ruleSets.isNotEmpty ? ' ${ruleSets.toString()}' : '';
+  String get _ruleSets => ruleSets.isNotEmpty ? ' $ruleSets' : '';
 
   String toMarkdown(List<Detail> details) {
     var sb = StringBuffer('| ');
@@ -311,7 +311,7 @@
       for (var bug in bugs) {
         var title = bug.title;
         if (title.contains(lint.name)) {
-          bugReferences.add('#${bug.number.toString()}');
+          bugReferences.add('#${bug.number}');
         }
       }
 
@@ -363,12 +363,12 @@
   static Future<List<String>> _getLintsWithBulkFixes() async {
     var client = http.Client();
     var req = await client.get(Uri.parse(
-        'https://raw.githubusercontent.com/dart-lang/sdk/master/pkg/analysis_server/lib/src/services/correction/fix_internal.dart'));
+        'https://raw.githubusercontent.com/dart-lang/sdk/main/pkg/analysis_server/lib/src/services/correction/fix_internal.dart'));
 
     var parser = CompilationUnitParser();
     var cu = parser.parse(contents: req.body, name: 'fix_internal.dart');
     var fixProcessor = cu.declarations.firstWhere(
-        (m) => m is ClassDeclaration && m.name.name == 'FixProcessor');
+        (m) => m is ClassDeclaration && m.name.lexeme == 'FixProcessor');
 
     var collector = _BulkFixCollector();
     fixProcessor.accept(collector);
@@ -378,12 +378,12 @@
   static Future<List<String>> _getLintsWithFixes() async {
     var client = http.Client();
     var req = await client.get(Uri.parse(
-        'https://raw.githubusercontent.com/dart-lang/sdk/master/pkg/analysis_server/lib/src/services/linter/lint_names.dart'));
+        'https://raw.githubusercontent.com/dart-lang/sdk/main/pkg/analysis_server/lib/src/services/linter/lint_names.dart'));
 
     var parser = CompilationUnitParser();
     var cu = parser.parse(contents: req.body, name: 'lint_names.dart');
-    var lintNamesClass = cu.declarations
-        .firstWhere((m) => m is ClassDeclaration && m.name.name == 'LintNames');
+    var lintNamesClass = cu.declarations.firstWhere(
+        (m) => m is ClassDeclaration && m.name.lexeme == 'LintNames');
 
     var collector = _FixCollector();
     lintNamesClass.accept(collector);
@@ -394,7 +394,7 @@
       'https://raw.githubusercontent.com/dart-lang/lints/main/lib/core.yaml');
 
   static Future<List<String>> _readFlutterLints() async => _fetchLints(
-      'https://raw.githubusercontent.com/flutter/packages/master/packages/flutter_lints/lib/flutter.yaml');
+      'https://raw.githubusercontent.com/flutter/packages/main/packages/flutter_lints/lib/flutter.yaml');
 
   static List<String> _readLints(String contents) {
     var lintConfigs = processAnalysisOptionsFile(contents);
@@ -412,7 +412,7 @@
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
     for (var field in node.fields.variables) {
-      if (field.name.name == 'lintProducerMap') {
+      if (field.name.lexeme == 'lintProducerMap') {
         var initializer = field.initializer;
         if (initializer is SetOrMapLiteral) {
           for (var element in initializer.elements) {
@@ -451,7 +451,7 @@
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
     for (var v in node.fields.variables) {
-      addLint(v.name.name);
+      addLint(v.name.lexeme);
     }
   }
 }
diff --git a/tool/checks/driver.dart b/tool/checks/driver.dart
new file mode 100644
index 0000000..c636278
--- /dev/null
+++ b/tool/checks/driver.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io' as io;
+
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/generated/engine.dart' // ignore: implementation_imports
+    show
+        AnalysisErrorInfoImpl,
+        AnalysisOptionsImpl;
+import 'package:analyzer/src/lint/registry.dart'; // ignore: implementation_imports
+import 'package:cli_util/cli_logging.dart';
+import 'package:linter/src/analyzer.dart';
+import 'package:linter/src/formatter.dart';
+import 'package:path/path.dart' as path;
+
+import 'rules/visit_registered_nodes.dart';
+
+Future<void> main() async {
+  var rules =
+      path.normalize(io.File(path.join('lib', 'src', 'rules')).absolute.path);
+  await Driver([VisitRegisteredNodes()]).analyze([rules]);
+}
+
+class Driver {
+  Logger logger = Logger.standard();
+
+  List<LintRule> lints;
+  bool silent = false;
+
+  Driver(this.lints);
+
+  Future analyze(List<String> sources, {bool displayTiming = false}) {
+    var analysisFuture = _analyze(sources);
+    if (!displayTiming) return analysisFuture;
+
+    var stopwatch = Stopwatch()..start();
+    return analysisFuture.then((value) {
+      _print(
+          '(Elapsed time: ${Duration(milliseconds: stopwatch.elapsedMilliseconds)})');
+    });
+  }
+
+  Future _analyze(List<String> sources) async {
+    if (sources.isEmpty) {
+      _print('Specify one or more files and directories.');
+      return;
+    }
+    ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE;
+    await _analyzeFiles(resourceProvider, sources);
+    _print('Finished.');
+  }
+
+  Future _analyzeFiles(
+      ResourceProvider resourceProvider, List<String> analysisRoots) async {
+    _print('Analyzing...');
+
+    // Register our checks.
+    lints.forEach(Registry.ruleRegistry.register);
+
+    for (var root in analysisRoots) {
+      var collection = AnalysisContextCollection(
+        includedPaths: [root],
+        resourceProvider: resourceProvider,
+      );
+
+      var errors = <AnalysisErrorInfo>[];
+
+      for (var context in collection.contexts) {
+        // Add lints.
+        var options = context.analysisOptions as AnalysisOptionsImpl;
+        options.lintRules = context.analysisOptions.lintRules.toList();
+        lints.forEach(options.lintRules.add);
+        options.lint = true;
+
+        for (var filePath in context.contextRoot.analyzedFiles()) {
+          if (isDartFileName(filePath)) {
+            try {
+              var result = await context.currentSession.getErrors(filePath);
+              if (result is ErrorsResult) {
+                var filtered = result.errors
+                    .where((e) => e.errorCode.name != 'TODO')
+                    .toList();
+                if (filtered.isNotEmpty) {
+                  errors.add(AnalysisErrorInfoImpl(filtered, result.lineInfo));
+                }
+              }
+            } on Exception catch (e) {
+              _print('Exception caught analyzing: $filePath');
+              _print(e.toString());
+            }
+          }
+        }
+      }
+      ReportFormatter(errors, null /*_TodoFilter()*/, io.stdout).write();
+
+      if (errors.isNotEmpty) {
+        io.exitCode = 3;
+      }
+    }
+  }
+
+  /// Pass the following [msg] to the [logger] instance iff [silent] is false.
+  void _print(String msg) {
+    if (!silent) {
+      logger.stdout(msg);
+    }
+  }
+
+  /// Returns `true` if this [fileName] is a Dart file.
+  static bool isDartFileName(String fileName) => fileName.endsWith('.dart');
+}
diff --git a/tool/checks/rules/visit_registered_nodes.dart b/tool/checks/rules/visit_registered_nodes.dart
new file mode 100644
index 0000000..2ba7a05
--- /dev/null
+++ b/tool/checks/rules/visit_registered_nodes.dart
@@ -0,0 +1,132 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:linter/src/analyzer.dart';
+
+const _desc = r'Declare visit methods for all registered node types.';
+
+const _details = r'''
+**DO** declare a visit method for all registered node processors.
+
+**BAD:**
+```dart
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addFunctionDeclaration(this, visitor);
+    registry.addFunctionTypeAlias(this, visitor);
+    registry.addMethodDeclaration(this, visitor);
+  }
+  
+  class _Visitor {
+    @override
+    void visitFunctionDeclaration(FunctionDeclaration node) {
+        // ...
+    }
+  }
+```
+
+**GOOD:**
+**BAD:**
+```dart
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addFunctionDeclaration(this, visitor);
+    registry.addFunctionTypeAlias(this, visitor);
+    registry.addMethodDeclaration(this, visitor);
+  }
+  
+  class _Visitor {
+    @override
+    void visitFunctionDeclaration(FunctionDeclaration node) {
+      // ...
+    }
+    
+    @override
+    void visitFunctionTypeAlias(FunctionTypeAlias node) {
+      // ...
+    }
+
+    @override
+    void visitMethodDeclaration(MethodDeclaration node) {
+      // ...
+    }
+  }
+```
+''';
+
+class VisitRegisteredNodes extends LintRule {
+  VisitRegisteredNodes()
+      : super(
+            name: 'visit_registered_nodes',
+            description: _desc,
+            details: _details,
+            group: Group.errors);
+
+  @override
+  void registerNodeProcessors(
+      NodeLintRegistry registry, LinterContext context) {
+    var visitor = _Visitor(this);
+    registry.addMethodDeclaration(this, visitor);
+  }
+}
+
+class _BodyVisitor extends RecursiveAstVisitor<void> {
+  final LintRule rule;
+  _BodyVisitor(this.rule);
+
+  bool implements(ClassElement visitor, String methodName) {
+    if (visitor.getMethod(methodName) != null) {
+      return true;
+    }
+
+    var method =
+        visitor.lookUpInheritedConcreteMethod(methodName, visitor.library);
+    // In general lint visitors should only inherit from SimpleAstVisitors
+    // (and the method implementations inherited from there are only stubs).
+    // (We might consider enforcing this since it's harder to ensure that
+    // Unifying and Generalizing visitors are doing the right thing.)
+    // For now we flag methods inherited from SimpleAstVisitor since they
+    // surely don't do anything.
+    return method?.enclosingElement.name != 'SimpleAstVisitor';
+  }
+
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    var targetType = node.target?.staticType;
+    if (targetType is! InterfaceType) return;
+    if (targetType.element.name != 'NodeLintRegistry') return;
+    var methodName = node.methodName.name;
+    if (!methodName.startsWith('add')) return;
+    var nodeType = methodName.substring(3);
+    var args = node.argumentList.arguments;
+    var argType = args[1].staticType;
+    if (argType is! InterfaceType) return;
+    var visitor = argType.element;
+    if (visitor is! ClassElement) return;
+    if (implements(visitor, 'visit$nodeType')) return;
+
+    rule.reportLint(node.methodName);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitMethodDeclaration(MethodDeclaration node) {
+    if (node.name.lexeme == 'registerNodeProcessors') {
+      node.body.accept(_BodyVisitor(rule));
+    }
+  }
+}
diff --git a/tool/crawl.dart b/tool/crawl.dart
index 81ae4e8..b093c25 100644
--- a/tool/crawl.dart
+++ b/tool/crawl.dart
@@ -20,17 +20,12 @@
 
 /// We don't care about SDKs previous to this bottom.
 final Version bottomDartSdk = Version(2, 0, 0);
-Map<String, String?> _dartSdkToLinterMap = <String, String?>{};
+final Map<String, String?> _dartSdkToLinterMap = <String, String?>{};
 
-final _effectiveDartOptionsRootUrl = Uri.https(
-    'raw.githubusercontent.com', '/tenhobi/effective_dart/master/lib/');
-final _effectiveDartOptionsUrl =
-    _effectiveDartOptionsRootUrl.resolve('analysis_options.yaml');
-List<String>? _effectiveDartRules;
 final _flutterOptionsUrl = Uri.https('raw.githubusercontent.com',
-    '/flutter/packages/master/packages/flutter_lints/lib/flutter.yaml');
-final _flutterRepoOptionsUrl = Uri.https('raw.githubusercontent.com',
-    '/flutter/flutter/master/analysis_options.yaml');
+    '/flutter/packages/main/packages/flutter_lints/lib/flutter.yaml');
+final _flutterRepoOptionsUrl = Uri.https(
+    'raw.githubusercontent.com', '/flutter/flutter/main/analysis_options.yaml');
 
 List<String>? _flutterRepoRules;
 
@@ -40,21 +35,13 @@
 Iterable<LintRule>? _registeredLints;
 final _repoPathPrefix =
     Uri.https('raw.githubusercontent.com', '/dart-lang/linter/');
-final _rulePathPrefix =
-    Uri.https('raw.githubusercontent.com', '/dart-lang/linter/');
 
 List<String>? _sdkTags;
 
+List<String>? _linterTags;
+
 Map<String, List<String?>> _sinceMap = <String, List<String>>{};
 
-final _stagehandOptionsUrl = Uri.https('raw.githubusercontent.com',
-    '/dart-lang/stagehand/master/templates/analysis_options.yaml');
-
-List<String>? _stagehandRules;
-
-Future<List<String>> get effectiveDartRules async =>
-    _effectiveDartRules ??= await _fetchEffectiveDartRules();
-
 Future<List<String>> get flutterRepoRules async =>
     _flutterRepoRules ??= await score_utils.fetchRules(_flutterRepoOptionsUrl);
 
@@ -64,8 +51,6 @@
 Future<int> get latestMinor async =>
     _latestMinor ??= await _readLatestMinorVersion();
 
-Future<List<String>> get pedanticRules async => score_utils.pedanticRules;
-
 Iterable<LintRule> get registeredLints {
   if (_registeredLints == null) {
     registerLintRules();
@@ -74,9 +59,6 @@
   return _registeredLints!;
 }
 
-Future<List<String>> get stagehandRules async =>
-    _stagehandRules ??= await score_utils.fetchRules(_stagehandOptionsUrl);
-
 Future<String?> dartSdkForLinter(String version, Authentication? auth) async {
   var sdkVersions = <String>[];
   var sdks = await getSdkTags(auth);
@@ -98,58 +80,34 @@
         {Authentication? auth}) async =>
     await dartSdkForLinter(linterVersion, auth);
 
-Future<String?> findSinceLinter(String lint, {Authentication? auth}) async {
-  // History recorded in `all.yaml` starts in minor 31.
-  var rules_31 = await rulesForVersion(31);
-  if (rules_31 != null) {
-    if (rules_31.contains(lint)) {
-      var version = await _crawlForVersion(lint);
-      if (version != null) {
+Future<String?> findSinceLinter(String lint, [Authentication? auth]) async {
+  var linterReleases = await getLinterReleases(auth);
+
+  for (var version in linterReleases) {
+    var rules = await rulesForVersion(version);
+    if (rules != null) {
+      if (rules.contains(lint)) {
         return version;
       }
     }
   }
 
-  var latest = await latestMinor;
-  for (var minor = 31; minor <= latest; ++minor) {
-    var rules = await rulesForVersion(minor);
-    if (rules != null) {
-      if (rules.contains(lint)) {
-        return '0.1.$minor';
-      }
-    }
-  }
-
   return null;
 }
 
-Future<List<String>> getSdkTags(Authentication? auth) async =>
-    _sdkTags ??= await _fetchSdkTags(auth);
+Future<List<String>> getSdkTags(Authentication? auth,
+        {bool onlyStable = false}) async =>
+    _sdkTags ??= await _fetchSdkTags(auth, onlyStable: onlyStable);
+
+Future<List<String>> getLinterReleases([Authentication? auth]) async =>
+    _linterTags ??=
+        (await _fetchLinterReleaseTags(auth)).reversed.toList(growable: false);
 
 Future<String?> linterForDartSdk(String sdk) async =>
     _dartSdkToLinterMap[sdk] ??= await _fetchLinterForVersion(sdk);
 
-Future<List<String?>?> rulesForVersion(int minor) async {
-  var version = '0.1.$minor';
-  if (minor >= 31) {
-    var rules = await fetchRulesForVersion(version);
-    return _sinceMap[version] ??= rules;
-  }
-  return null;
-}
-
-Future<String?> _crawlForVersion(String lint) async {
-  var client = http.Client();
-  for (var minor = 1; minor < 31; ++minor) {
-    var version = '0.1.$minor';
-    var req = await client
-        .get(_rulePathPrefix.resolve('$version/lib/src/rules/$lint.dart'));
-    if (req.statusCode == 200) {
-      return version;
-    }
-  }
-  return null;
-}
+Future<List<String?>?> rulesForVersion(String version) async =>
+    _sinceMap[version] ??= await fetchRulesForVersion(version);
 
 Future<String> _fetchDEPSforVersion(String version) async {
   var client = http.Client();
@@ -159,33 +117,90 @@
   return req.body;
 }
 
-Future<List<String>> _fetchEffectiveDartRules() async {
-  var client = http.Client();
-  var req = await client.get(_effectiveDartOptionsUrl);
-  var includedOptions =
-      req.body.split('include: package:effective_dart/')[1].trim();
-  return score_utils
-      .fetchRules(_effectiveDartOptionsRootUrl.resolve(includedOptions));
+final _linterReleasePattern = RegExp(r'\d+\.\d+(\.\d+)');
+
+Future<String?> _commitReferenceToVersion(String version) async {
+  if (_linterReleasePattern.hasMatch(version)) {
+    return version;
+  }
+
+  // Get all tags which include this commit reference
+  var result = await Process.run('git', ['tag', '--contains', version]);
+  var output = result.stdout;
+  if (output is String) {
+    var tags = _linterReleasePattern.allMatches(output);
+    // Take the earliest (first) release which includes this commit
+    var latestTag = tags.first.group(0);
+    if (latestTag != null) {
+      return latestTag;
+    }
+  }
+
+  return null;
 }
 
 Future<String?> _fetchLinterForVersion(String version) async {
   var deps = await _fetchDEPSforVersion(version);
-  for (var line in deps.split('\n')) {
-    if (line.trim().startsWith('"lint')) {
+  for (var untrimmedLine in deps.split('\n')) {
+    var line = untrimmedLine.trim();
+    if (line.startsWith('"lint')) {
       // "linter_tag": "0.1.59",
-      var split = line.trim().split('"linter_tag":');
-      if (split.length == 2) {
+      var oldSplit = line.split('"linter_tag":');
+      if (oldSplit.length == 2) {
         //  "0.1.59",
-        return split[1].split('"')[1];
+        return _commitReferenceToVersion(oldSplit[1].split('"')[1]);
+      }
+
+      // "linter_rev": "f2c55484e8ebda0aec8c2fea637b3bd5b17258ca",
+      var newSplit = line.split('"linter_rev":');
+      if (newSplit.length == 2) {
+        // "f2c55484e8ebda0aec8c2fea637b3bd5b17258ca",
+        var parsedLinterVersion = newSplit[1].split('"')[1];
+        return _commitReferenceToVersion(parsedLinterVersion);
       }
     }
   }
   return null;
 }
 
-Future<List<String>> _fetchSdkTags(Authentication? auth) async {
+final _releaseTagPattern = RegExp(r'\d+');
+final _stableReleasePattern = RegExp(r'^\d+\.\d+\.\d+$');
+
+Future<List<String>> _fetchLinterReleaseTags(Authentication? auth) async =>
+    await _fetchRepoTags(
+        'dart-lang', 'linter', auth, _stableReleasePattern.hasMatch);
+
+Future<List<String>> _fetchSdkTags(Authentication? auth,
+        {bool onlyStable = false}) async =>
+    await _fetchRepoTags('dart-lang', 'linter', auth, (t) {
+      // Filter on numeric release tags.
+      if (!t.startsWith(_releaseTagPattern)) {
+        return false;
+      }
+
+      // Filter on bottom.
+      try {
+        var version = Version.parse(t);
+        if (version < bottomDartSdk) {
+          return false;
+        }
+      } on FormatException {
+        return false;
+      }
+
+      if (onlyStable) {
+        if (!_stableReleasePattern.hasMatch(t)) {
+          return false;
+        }
+      }
+
+      return true;
+    });
+
+Future<List<String>> _fetchRepoTags(String org, String repo,
+    Authentication? auth, bool Function(String) where) async {
   var github = GitHub(auth: auth);
-  var slug = RepositorySlug('dart-lang', 'sdk');
+  var slug = RepositorySlug(org, repo);
 
   print('list repository tags: $slug');
   print('authentication:  ${auth != null ? "(token)" : "(anonymous)"}');
@@ -195,37 +210,19 @@
       .map((t) => t.name)
       .toList()
       .catchError((e) {
-    print('exception caught fetching SDK tags');
+    print('exception caught fetching $repo tags');
     print(e);
-    print('(using cached SDK values)');
+    print('(using cached $repo values)');
     return Future.value(<String>[]);
   });
 
-  return tags.whereType<String>().where((t) {
-    // Filter on numeric release tags.
-    if (!t.startsWith(RegExp(r'\d+'))) {
-      return false;
-    }
-
-    // Filter on bottom.
-    try {
-      var version = Version.parse(t);
-      return version.compareTo(bottomDartSdk) >= 0;
-    } on FormatException {
-      return false;
-    }
-  }).toList();
+  return tags.whereType<String>().where(where).toList(growable: false);
 }
 
 Future<int> _readLatestMinorVersion() async {
   var contents = await File('pubspec.yaml').readAsString();
   var pubspec = loadYamlNode(contents) as YamlMap;
-  // 0.1.79 or 0.1.79-dev or 0.1.97+1
-  return int.parse((pubspec['version'] as String)
-      .split('.')
-      .last
-      .split('-')
-      .first
-      .split('+')
-      .first);
+  var version = pubspec['version'] as String;
+  // 1.15.0
+  return int.parse(version.split('.')[1]);
 }
diff --git a/tool/doc.dart b/tool/doc.dart
index 43abdf8..1637653 100644
--- a/tool/doc.dart
+++ b/tool/doc.dart
@@ -13,6 +13,7 @@
 import 'package:linter/src/analyzer.dart';
 import 'package:linter/src/rules.dart';
 import 'package:markdown/markdown.dart';
+import 'package:yaml/yaml.dart';
 
 import 'machine.dart';
 import 'since.dart';
@@ -21,7 +22,13 @@
 void main(List<String> args) async {
   var parser = ArgParser()
     ..addOption('out', abbr: 'o', help: 'Specifies output directory.')
-    ..addOption('token', abbr: 't', help: 'Specifies a github auth token.');
+    ..addOption('token', abbr: 't', help: 'Specifies a GitHub auth token.')
+    ..addFlag('create-dirs',
+        abbr: 'd', help: 'Enables creation of necessary directories.')
+    ..addFlag('markdown',
+        abbr: 'm',
+        help: 'Enables generation of the markdown docs.',
+        defaultsTo: true);
 
   ArgResults options;
   try {
@@ -36,7 +43,13 @@
   var token = options['token'];
   var auth = token is String ? Authentication.withToken(token) : null;
 
-  await generateDocs(outDir, auth: auth);
+  var createDirectories = options['create-dirs'] == true;
+  var enableMarkdown = options['markdown'] == true;
+
+  await generateDocs(outDir,
+      auth: auth,
+      createDirectories: createDirectories,
+      enableMarkdown: enableMarkdown);
 }
 
 const ruleFootMatter = '''
@@ -57,9 +70,9 @@
 
 Many lints are included in various predefined rulesets:
 
-* [pedantic](https://github.com/dart-lang/pedantic) for rules enforced internally at Google
-* [effective_dart](https://github.com/tenhobi/effective_dart) for rules corresponding to the [Effective Dart](https://dart.dev/guides/language/effective-dart) style guide
-* [flutter](https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml) for rules recommended for Flutter projects (`flutter create` enables these by default)
+* [core](https://github.com/dart-lang/lints) for official "core" Dart team lint rules.
+* [recommended](https://github.com/dart-lang/lints) for additional lint rules "recommended" by the Dart team.
+* [flutter](https://github.com/flutter/packages/blob/main/packages/flutter_lints/lib/flutter.yaml) for rules recommended for Flutter projects (`flutter create` enables these by default).
 
 Rules included in these rulesets are badged in the documentation below.
 
@@ -70,9 +83,7 @@
 const ruleLeadMatter = 'Rules are organized into familiar rule groups.';
 
 final coreRules = <String?>[];
-final effectiveDartRules = <String?>[];
 final flutterRules = <String?>[];
-final pedanticRules = <String?>[];
 final recommendedRules = <String?>[];
 
 /// Sorted list of contributed lint rules.
@@ -81,46 +92,14 @@
 
 late Map<String, SinceInfo> sinceInfo;
 
-Future<String> get effectiveDartLatestVersion async {
-  var url =
-      'https://raw.githubusercontent.com/tenhobi/effective_dart/master/lib/analysis_options.yaml';
-  var client = http.Client();
-  print('loading $url...');
-  var req = await client.get(Uri.parse(url));
-  var parts = req.body.split('package:effective_dart/analysis_options.');
-  return parts[1].split('.yaml')[0];
-}
-
-String get enumerateErrorRules =>
-    rules.where((r) => r.group == Group.errors).map(toDescription).join('\n\n');
-
-String get enumerateGroups => Group.builtin
-    .map((Group g) =>
-        '<li><strong>${g.name} -</strong> ${markdownToHtml(g.description)}</li>')
-    .join('\n');
-
-String get enumeratePubRules =>
-    rules.where((r) => r.group == Group.pub).map(toDescription).join('\n\n');
-
-String get enumerateStyleRules =>
-    rules.where((r) => r.group == Group.style).map(toDescription).join('\n\n');
-
-Future<String> get pedanticLatestVersion async {
-  var url =
-      'https://raw.githubusercontent.com/dart-lang/pedantic/master/lib/analysis_options.yaml';
-  var client = http.Client();
-  print('loading $url...');
-  var req = await client.get(Uri.parse(url));
-  var parts = req.body.split('package:pedantic/analysis_options.');
-  return parts[1].split('.yaml')[0];
-}
+final Map<String, String> _fixStatusMap = <String, String>{};
 
 String describeMaturity(LintRule r) =>
     r.maturity == Maturity.stable ? '' : ' (${r.maturity.name})';
 
 Future<void> fetchBadgeInfo() async {
   var core = await fetchConfig(
-      'https://raw.githubusercontent.com/dart-lang/lints/master/lib/core.yaml');
+      'https://raw.githubusercontent.com/dart-lang/lints/main/lib/core.yaml');
   if (core != null) {
     for (var ruleConfig in core.ruleConfigs) {
       coreRules.add(ruleConfig.name);
@@ -128,7 +107,7 @@
   }
 
   var recommended = await fetchConfig(
-      'https://raw.githubusercontent.com/dart-lang/lints/master/lib/recommended.yaml');
+      'https://raw.githubusercontent.com/dart-lang/lints/main/lib/recommended.yaml');
   if (recommended != null) {
     recommendedRules.addAll(coreRules);
     for (var ruleConfig in recommended.ruleConfigs) {
@@ -136,26 +115,8 @@
     }
   }
 
-  var latestPedantic = await pedanticLatestVersion;
-  var pedantic = await fetchConfig(
-      'https://raw.githubusercontent.com/dart-lang/pedantic/master/lib/analysis_options.$latestPedantic.yaml');
-  if (pedantic != null) {
-    for (var ruleConfig in pedantic.ruleConfigs) {
-      pedanticRules.add(ruleConfig.name);
-    }
-  }
-
-  var latestEffectiveDart = await effectiveDartLatestVersion;
-  var effectiveDart = await fetchConfig(
-      'https://raw.githubusercontent.com/tenhobi/effective_dart/master/lib/analysis_options.$latestEffectiveDart.yaml');
-  if (effectiveDart != null) {
-    for (var ruleConfig in effectiveDart.ruleConfigs) {
-      effectiveDartRules.add(ruleConfig.name);
-    }
-  }
-
   var flutter = await fetchConfig(
-      'https://raw.githubusercontent.com/flutter/packages/master/packages/flutter_lints/lib/flutter.yaml');
+      'https://raw.githubusercontent.com/flutter/packages/main/packages/flutter_lints/lib/flutter.yaml');
   if (flutter != null) {
     flutterRules.addAll(recommendedRules);
     for (var ruleConfig in flutter.ruleConfigs) {
@@ -171,24 +132,55 @@
   return processAnalysisOptionsFile(req.body);
 }
 
+Future<Map<String, String>> fetchFixStatusMap() async {
+  if (_fixStatusMap.isNotEmpty) return _fixStatusMap;
+  var url =
+      'https://raw.githubusercontent.com/dart-lang/sdk/main/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml';
+  var client = http.Client();
+  print('loading $url...');
+  var req = await client.get(Uri.parse(url));
+  var yaml = loadYamlNode(req.body) as YamlMap;
+  for (var entry in yaml.entries) {
+    var code = entry.key as String;
+    if (code.startsWith('LintCode.')) {
+      _fixStatusMap[code.substring(9)] =
+          (entry.value as YamlMap)['status'] as String;
+    }
+  }
+  return _fixStatusMap;
+}
+
 Future<void> fetchSinceInfo(Authentication? auth) async {
   sinceInfo = await getSinceMap(auth);
 }
 
-Future<void> generateDocs(String? dir, {Authentication? auth}) async {
+Future<void> generateDocs(String? dir,
+    {Authentication? auth,
+    bool createDirectories = false,
+    bool enableMarkdown = true}) async {
   var outDir = dir;
   if (outDir != null) {
     var d = Directory(outDir);
+    if (createDirectories) {
+      d.createSync();
+    }
+
     if (!d.existsSync()) {
       print("Directory '${d.path}' does not exist");
       return;
     }
+
     if (!File('$outDir/options').existsSync()) {
       var lintsChildDir = Directory('$outDir/lints');
       if (lintsChildDir.existsSync()) {
         outDir = lintsChildDir.path;
       }
     }
+
+    if (createDirectories) {
+      Directory('$outDir/options').createSync();
+      Directory('$outDir/machine').createSync();
+    }
   }
 
   registerLintRules();
@@ -202,24 +194,34 @@
   // Fetch since info.
   await fetchSinceInfo(auth);
 
+  var fixStatusMap = await fetchFixStatusMap();
+
   // Generate rule files.
-  for (var l in rules) {
-    RuleHtmlGenerator(l).generate(outDir);
-    RuleMarkdownGenerator(l).generate(filePath: outDir);
+  for (var rule in rules) {
+    var fixStatus = getFixStatus(rule, fixStatusMap);
+    RuleHtmlGenerator(rule, fixStatus).generate(outDir);
+    if (enableMarkdown) {
+      RuleMarkdownGenerator(rule)
+          .generate(filePath: outDir, fixStatus: fixStatus);
+    }
   }
 
   // Generate index.
-  HtmlIndexer(Registry.ruleRegistry).generate(outDir);
-  MarkdownIndexer(Registry.ruleRegistry).generate(filePath: outDir);
+  HtmlIndexer(Registry.ruleRegistry, fixStatusMap).generate(outDir);
+
+  if (enableMarkdown) {
+    MarkdownIndexer(Registry.ruleRegistry, fixStatusMap)
+        .generate(filePath: outDir);
+  }
 
   // Generate options samples.
   OptionsSample(rules).generate(outDir);
 
   // Generate a machine-readable summary of rules.
-  MachineSummaryGenerator(Registry.ruleRegistry).generate(outDir);
+  MachineSummaryGenerator(Registry.ruleRegistry, fixStatusMap).generate(outDir);
 }
 
-String getBadges(String rule) {
+String getBadges(String rule, [String? fixStatus]) {
   var sb = StringBuffer();
   if (coreRules.contains(rule)) {
     sb.write(
@@ -233,22 +235,28 @@
   }
   if (flutterRules.contains(rule)) {
     sb.write(
-        '<a class="style-type" href="https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml">'
+        '<a class="style-type" href="https://github.com/flutter/packages/blob/main/packages/flutter_lints/lib/flutter.yaml">'
         '<!--suppress HtmlUnknownTarget --><img alt="flutter" src="style-flutter.svg"></a>');
   }
-  if (pedanticRules.contains(rule)) {
+  if (fixStatus == 'hasFix') {
     sb.write(
-        '<a class="style-type" href="https://github.com/dart-lang/pedantic/#enabled-lints">'
-        '<!--suppress HtmlUnknownTarget --><img alt="pedantic" src="style-pedantic.svg"></a>');
-  }
-  if (effectiveDartRules.contains(rule)) {
-    sb.write(
-        '<a class="style-type" href="https://github.com/tenhobi/effective_dart">'
-        '<!--suppress HtmlUnknownTarget --><img alt="effective dart" src="style-effective_dart.svg"></a>');
+        '<a class="style-type" href="https://medium.com/dartlang/quick-fixes-for-analysis-issues-c10df084971a">'
+        '<!--suppress HtmlUnknownTarget --><img alt="has-fix" src="has-fix.svg"></a>');
   }
   return sb.toString();
 }
 
+String getFixStatus(LintRule rule, Map<String, String> fixStatusMap) {
+  var fallback = 'unregistered';
+  for (var code in rule.lintCodes) {
+    var status = fixStatusMap[code.uniqueName.substring(9)];
+    if (status == null) continue;
+    if (status == 'hasFix') return status;
+    fallback = status;
+  }
+  return fallback;
+}
+
 void printUsage(ArgParser parser, [String? error]) {
   var message = 'Generates lint docs.';
   if (error != null) {
@@ -263,9 +271,6 @@
 
 String qualify(LintRule r) => r.name + describeMaturity(r);
 
-String toDescription(LintRule r) =>
-    '<!--suppress HtmlUnknownTarget --><strong><a href = "${r.name}.html">${qualify(r)}</a></strong><br/> ${getBadges(r.name)} ${markdownToHtml(r.description)}';
-
 class CountBadger {
   Iterable<LintRule> rules;
 
@@ -284,8 +289,26 @@
 
 class HtmlIndexer {
   final Iterable<LintRule> rules;
+  final Map<String, String> fixStatusMap;
+  HtmlIndexer(this.rules, this.fixStatusMap);
 
-  HtmlIndexer(this.rules);
+  String get enumerateErrorRules => rules
+      .where((r) => r.group == Group.errors)
+      .map(toDescription)
+      .join('\n\n');
+
+  String get enumerateGroups => Group.builtin
+      .map((Group g) =>
+          '<li><strong>${g.name} -</strong> ${markdownToHtml(g.description)}</li>')
+      .join('\n');
+
+  String get enumeratePubRules =>
+      rules.where((r) => r.group == Group.pub).map(toDescription).join('\n\n');
+
+  String get enumerateStyleRules => rules
+      .where((r) => r.group == Group.style)
+      .map(toDescription)
+      .join('\n\n');
 
   void generate(String? filePath) {
     var generated = _generate();
@@ -298,6 +321,9 @@
     }
   }
 
+  String toDescription(LintRule r) =>
+      '<!--suppress HtmlUnknownTarget --><strong><a href = "${r.name}.html">${qualify(r)}</a></strong><br/> ${getBadges(r.name, fixStatusMap[r.name])} ${markdownToHtml(r.description)}';
+
   String _generate() => '''
 <!DOCTYPE html>
 <html lang="en">
@@ -350,7 +376,7 @@
       </div>
       <footer>
          <p>Maintained by the <a href="https://dart.dev/">Dart Team</a></p>
-         <p>Visit us on <a href="https://github.com/dart-lang/linter">Github</a></p>
+         <p>Visit us on <a href="https://github.com/dart-lang/linter">GitHub</a></p>
       </footer>
    </body>
 </html>
@@ -359,11 +385,13 @@
 
 class MachineSummaryGenerator {
   final Iterable<LintRule> rules;
+  final Map<String, String> fixStatusMap;
 
-  MachineSummaryGenerator(this.rules);
+  MachineSummaryGenerator(this.rules, this.fixStatusMap);
 
   void generate(String? filePath) {
-    var generated = getMachineListing(rules);
+    var generated = getMachineListing(rules,
+        fixStatusMap: fixStatusMap, sinceInfo: sinceInfo);
     if (filePath != null) {
       var outPath = '$filePath/machine/rules.json';
       print('Writing to $outPath');
@@ -376,8 +404,9 @@
 
 class MarkdownIndexer {
   final Iterable<LintRule> rules;
+  final Map<String, String> fixStatusMap;
 
-  MarkdownIndexer(this.rules);
+  MarkdownIndexer(this.rules, this.fixStatusMap);
 
   void generate({String? filePath}) {
     var buffer = StringBuffer();
@@ -417,17 +446,14 @@
       }
       if (flutterRules.contains(rule.name)) {
         buffer.writeln('[![flutter](style-flutter.svg)]'
-            '(https://github.com/flutter/packages/blob/master/packages/'
+            '(https://github.com/flutter/packages/blob/main/packages/'
             'flutter_lints/lib/flutter.yaml)');
       }
-      if (pedanticRules.contains(rule.name)) {
-        buffer.writeln('[![pedantic](style-pedantic.svg)]'
-            '(https://github.com/dart-lang/pedantic/#enabled-lints)');
+      if (fixStatusMap[rule.name] == 'hasFix') {
+        buffer.writeln('[![has-fix](has-fix.svg)]'
+            '(https://medium.com/dartlang/quick-fixes-for-analysis-issues-c10df084971a)');
       }
-      if (effectiveDartRules.contains(rule.name)) {
-        buffer.writeln('[![effective dart](style-effective_dart.svg)]'
-            '(https://github.com/tenhobi/effective_dart)');
-      }
+
       buffer.writeln();
     }
 
@@ -453,7 +479,7 @@
     }
 
     if (filePath == null) {
-      print(buffer.toString());
+      print(buffer);
     } else {
       File('$filePath/index.md').writeAsStringSync(buffer.toString());
     }
@@ -538,7 +564,7 @@
       </div>
       <footer>
          <p>Maintained by the <a href="https://dart.dev/">Dart Team</a></p>
-         <p>Visit us on <a href="https://github.com/dart-lang/linter">Github</a></p>
+         <p>Visit us on <a href="https://github.com/dart-lang/linter">GitHub</a></p>
       </footer>
    </body>
 </html>
@@ -547,8 +573,9 @@
 
 class RuleHtmlGenerator {
   final LintRule rule;
+  final String fixStatus;
 
-  RuleHtmlGenerator(this.rule);
+  RuleHtmlGenerator(this.rule, this.fixStatus);
 
   String get details => rule.details;
 
@@ -592,10 +619,13 @@
 
   String get since {
     var info = sinceInfo[name]!;
-    var version = info.sinceDartSdk != null
+    var sdkVersion = info.sinceDartSdk != null
         ? '>= ${info.sinceDartSdk}'
-        : '<strong>unreleased</strong>';
-    return 'Dart SDK: $version • <small>(Linter v${info.sinceLinter})</small>';
+        : '<strong>Unreleased</strong>';
+    var linterVersion = info.sinceLinter != null
+        ? 'v${info.sinceLinter}'
+        : '<strong>Unreleased</strong>';
+    return 'Dart SDK: $sdkVersion • <small>(Linter $linterVersion)</small>';
   }
 
   void generate([String? filePath]) {
@@ -631,7 +661,7 @@
                <p>$since</p>
                <span class="tooltip-content">Since info is static, may be stale</span>
             </div>
-            ${getBadges(name)}
+            ${getBadges(name, fixStatus)}
             <ul>
                <li><a href="index.html">View all <strong>Lint Rules</strong></a></li>
                <li><a href="https://dart.dev/guides/language/analysis-options#enabling-linter-rules">Using the <strong>Linter</strong></a></li>
@@ -647,7 +677,7 @@
       </div>
       <footer>
          <p>Maintained by the <a href="https://dart.dev/">Dart Team</a></p>
-         <p>Visit us on <a href="https://github.com/dart-lang/linter">Github</a></p>
+         <p>Visit us on <a href="https://github.com/dart-lang/linter">GitHub</a></p>
       </footer>
    </body>
 </html>
@@ -669,13 +699,15 @@
 
   String get since {
     var info = sinceInfo[name]!;
-    var version = info.sinceDartSdk != null
+    var sdkVersion = info.sinceDartSdk != null
         ? '>= ${info.sinceDartSdk}'
-        : '**unreleased**';
-    return 'Dart SDK: $version • (Linter v${info.sinceLinter})';
+        : '**Unreleased**';
+    var linterVersion =
+        info.sinceLinter != null ? 'v${info.sinceLinter}' : '**Unreleased**';
+    return 'Dart SDK: $sdkVersion • _(Linter $linterVersion)_';
   }
 
-  void generate({String? filePath}) {
+  void generate({String? filePath, String? fixStatus}) {
     var buffer = StringBuffer();
 
     buffer.writeln('# Rule $name');
@@ -696,16 +728,12 @@
     }
     if (flutterRules.contains(name)) {
       buffer.writeln('[![flutter](style-flutter.svg)]'
-          '(https://github.com/flutter/packages/blob/master/packages/'
+          '(https://github.com/flutter/packages/blob/main/packages/'
           'flutter_lints/lib/flutter.yaml)');
     }
-    if (pedanticRules.contains(name)) {
-      buffer.writeln('[![pedantic](style-pedantic.svg)]'
-          '(https://github.com/dart-lang/pedantic/#enabled-lints)');
-    }
-    if (effectiveDartRules.contains(name)) {
-      buffer.writeln('[![effective dart](style-effective_dart.svg)]'
-          '(https://github.com/tenhobi/effective_dart)');
+    if (fixStatus == 'hasFix') {
+      buffer.writeln('[![has-fix](has-fix.svg)]'
+          '(https://medium.com/dartlang/quick-fixes-for-analysis-issues-c10df084971a)');
     }
 
     buffer.writeln();
@@ -726,7 +754,7 @@
     }
 
     if (filePath == null) {
-      print(buffer.toString());
+      print(buffer);
     } else {
       File('$filePath/$name.md').writeAsStringSync(buffer.toString());
     }
diff --git a/tool/github.dart b/tool/github.dart
index e481025..c6f3824 100644
--- a/tool/github.dart
+++ b/tool/github.dart
@@ -10,7 +10,7 @@
   try {
     return github.issues.listByRepo(slug).toList();
   } on Exception catch (e) {
-    print('exception caught fetching github issues');
+    print('exception caught fetching GitHub issues');
     print(e);
     print('(defaulting to an empty list)');
     return Future.value(<Issue>[]);
diff --git a/tool/grind.dart b/tool/grind.dart
index 33c32c9..a59b860 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -10,7 +10,9 @@
 import 'rule.dart';
 
 @Deprecated('To be removed')
-void main(List<String> args) => grind(args);
+Future<void> main(List<String> args) async {
+  await grind(args);
+}
 
 Iterable<String> get sourcePaths => sources.map((dir) => dir.path);
 
@@ -26,10 +28,10 @@
     });
 
 @Task('Generate lint rule docs.')
-void docs() {
+Future<void> docs() async {
   var args = context.invocation.arguments;
   var dir = args.getOption('dir');
-  generateDocs(dir);
+  await generateDocs(dir);
 }
 
 @Task('Format linter sources.')
diff --git a/tool/labeler/issue_config.dart b/tool/labeler/issue_config.dart
new file mode 100644
index 0000000..291cac6
--- /dev/null
+++ b/tool/labeler/issue_config.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+import 'package:http/http.dart' as http;
+
+/// Generate issue labeler workflow config data.
+main(List<String> args) async {
+  var client = http.Client();
+  var req = await client.get(
+      Uri.parse('https://dart-lang.github.io/linter/lints/machine/rules.json'));
+
+  var machine = json.decode(req.body);
+
+  var coreLints = <String>[];
+  var recommendedLints = <String>[];
+  var flutterLints = <String>[];
+  for (var entry in machine) {
+    var sets = entry['sets'] as List;
+    if (sets.contains('core')) {
+      coreLints.add(entry['name'] as String);
+    } else if (sets.contains('recommended')) {
+      recommendedLints.add(entry['name'] as String);
+    } else if (sets.contains('flutter')) {
+      flutterLints.add(entry['name'] as String);
+    }
+  }
+
+  // todo(pq): consider a local cache of internally available rules.
+
+  print('# Auto-generated by `tool/labeler/issue_config.dart`');
+
+  print('\nset-core:');
+  print("  - '(${coreLints.sorted().join('|')})'");
+  print('\nset-recommended:');
+  print("  - '(${recommendedLints.sorted().join('|')})'");
+  print('\nset-flutter:');
+  print("  - '(${flutterLints.sorted().join('|')})'");
+}
diff --git a/tool/labeler/pr_config.dart b/tool/labeler/pr_config.dart
new file mode 100644
index 0000000..98af42e
--- /dev/null
+++ b/tool/labeler/pr_config.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+import 'package:http/http.dart' as http;
+
+/// Generate PR labeler workflow config data.
+main(List<String> args) async {
+  var client = http.Client();
+  var req = await client.get(
+      Uri.parse('https://dart-lang.github.io/linter/lints/machine/rules.json'));
+
+  var machine = json.decode(req.body);
+
+  var coreLints = <String>[];
+  var recommendedLints = <String>[];
+  var flutterLints = <String>[];
+  for (var entry in machine) {
+    var sets = entry['sets'] as List;
+    if (sets.contains('core')) {
+      coreLints.add(entry['name'] as String);
+    } else if (sets.contains('recommended')) {
+      recommendedLints.add(entry['name'] as String);
+    } else if (sets.contains('flutter')) {
+      flutterLints.add(entry['name'] as String);
+    }
+  }
+
+  // todo(pq): consider a local cache of internally available rules.
+
+  print('# Auto-generated by `tool/labeler/pr_config.dart`');
+
+  print('\nset-core:');
+  for (var lint in coreLints.sorted()) {
+    print('- lib/**/$lint.dart');
+  }
+  print('\nset-recommended:');
+  for (var lint in recommendedLints.sorted()) {
+    print('- lib/**/$lint.dart');
+  }
+  print('\nset-flutter:');
+  for (var lint in flutterLints.sorted()) {
+    print('- lib/**/$lint.dart');
+  }
+}
diff --git a/tool/machine.dart b/tool/machine.dart
index ffe0a69..55e1ac4 100644
--- a/tool/machine.dart
+++ b/tool/machine.dart
@@ -10,6 +10,7 @@
 import 'package:linter/src/rules.dart';
 
 import 'doc.dart';
+import 'since.dart';
 
 /// Generates a list of lint rules in machine format suitable for consumption by
 /// other tools.
@@ -30,10 +31,12 @@
 }
 
 String getMachineListing(Iterable<LintRule> ruleRegistry,
-    {bool pretty = true}) {
+    {Map<String, String>? fixStatusMap,
+    bool pretty = true,
+    Map<String, SinceInfo>? sinceInfo}) {
   var rules = List<LintRule>.of(ruleRegistry, growable: false)..sort();
   var encoder = pretty ? JsonEncoder.withIndent('  ') : JsonEncoder();
-
+  fixStatusMap ??= {};
   var json = encoder.convert([
     for (var rule in rules)
       {
@@ -46,10 +49,13 @@
           if (coreRules.contains(rule.name)) 'core',
           if (recommendedRules.contains(rule.name)) 'recommended',
           if (flutterRules.contains(rule.name)) 'flutter',
-          if (pedanticRules.contains(rule.name)) 'pedantic',
-          if (effectiveDartRules.contains(rule.name)) 'effective_dart',
         ],
+        'fixStatus': fixStatusMap[rule.name] ?? 'unregistered',
         'details': rule.details,
+        if (sinceInfo != null)
+          'sinceDartSdk': sinceInfo[rule.name]?.sinceDartSdk ?? 'Unreleased',
+        if (sinceInfo != null)
+          'sinceLinter': sinceInfo[rule.name]?.sinceLinter ?? 'Unreleased',
       }
   ]);
   return json;
diff --git a/tool/pana_baseline.dart b/tool/pana_baseline.dart
index 93cce0d..2036edd 100644
--- a/tool/pana_baseline.dart
+++ b/tool/pana_baseline.dart
@@ -14,12 +14,14 @@
   print(baseline);
 
   print('Installing pana...');
-  var activate = await Process.run('pub', ['global', 'activate', 'pana']);
+  var activate =
+      await Process.run('dart', ['pub', 'global', 'activate', 'pana']);
   expectOk(activate);
   print(activate.stdout);
 
   print('Running pana...');
-  var output = await Process.run('pub', [
+  var output = await Process.run('dart', [
+    'pub',
     'global',
     'run',
     'pana',
diff --git a/tool/query.dart b/tool/query.dart
index 0b1dd4a..4245a98 100644
--- a/tool/query.dart
+++ b/tool/query.dart
@@ -12,7 +12,7 @@
 
 Future<void> main(List<String> args) async {
   var parser = ArgParser()
-    ..addOption('token', abbr: 't', help: 'Specifies a github auth token.');
+    ..addOption('token', abbr: 't', help: 'Specifies a GitHub auth token.');
   ArgResults options;
   try {
     options = parser.parse(args);
diff --git a/tool/rule.dart b/tool/rule.dart
index 9c187af..2341be3 100644
--- a/tool/rule.dart
+++ b/tool/rule.dart
@@ -14,8 +14,12 @@
 void main(List<String> args) {
   var parser = ArgParser()
     ..addOption('out', abbr: 'o', help: 'Specifies project root.')
-    ..addOption('name',
-        abbr: 'n', help: 'Specifies lower_underscore rule name.');
+    ..addOption(
+      'name',
+      abbr: 'n',
+      help: 'Specifies lower_underscore rule name.',
+      mandatory: true,
+    );
 
   ArgResults options;
   try {
@@ -25,14 +29,11 @@
     return;
   }
 
-  var outDir = options['out'];
-
-  if (outDir != null) {
-    var d = Directory(outDir as String);
-    if (!d.existsSync()) {
-      print("Directory '${d.path}' does not exist");
-      return;
-    }
+  var outDir = options['out'] ?? '.';
+  var d = Directory(outDir as String);
+  if (!d.existsSync()) {
+    print("Directory '${d.path}' does not exist");
+    return;
   }
 
   var ruleName = options['name'];
@@ -43,7 +44,7 @@
   }
 
   // Generate rule stub.
-  generateRule(ruleName as String, outDir: outDir as String?);
+  generateRule(ruleName as String, outDir: outDir);
 }
 
 String get _thisYear => DateTime.now().year.toString();
@@ -62,7 +63,7 @@
   updateRuleRegistry(ruleName);
 }
 
-void generateStub(String ruleName, String stubPath, _Generator generator,
+void generateStub(String ruleName, String stubPath, Generator generator,
     {String? outDir}) {
   var generated = generator(ruleName, toClassName(ruleName));
   if (outDir != null) {
@@ -112,7 +113,6 @@
 const _desc = r' ';
 
 const _details = r'''
-
 **DO** ...
 
 **BAD:**
@@ -127,7 +127,7 @@
 
 ''';
 
-class $className extends LintRule implements NodeLintRule {
+class $className extends LintRule {
   $className()
       : super(
             name: '$ruleName',
@@ -163,4 +163,4 @@
 
 ''';
 
-typedef _Generator = String Function(String libName, String className);
+typedef Generator = String Function(String libName, String className);
diff --git a/tool/scorecard.dart b/tool/scorecard.dart
index 6135ba0..7d33453 100644
--- a/tool/scorecard.dart
+++ b/tool/scorecard.dart
@@ -23,8 +23,6 @@
     Detail.linter,
     Detail.sdk,
     Detail.fix,
-    Detail.pedantic,
-    Detail.effectiveDart,
     Detail.flutterUser,
     Detail.flutterRepo,
     Detail.status,
@@ -54,20 +52,12 @@
 }
 
 StringBuffer buildFooter(ScoreCard scorecard, List<Detail> details) {
-  var pedanticLintCount = 0;
   var flutterUserLintCount = 0;
   var flutterRepoLintCount = 0;
   var fixCount = 0;
-  var pedanticFixCount = 0;
 
   for (var score in scorecard.scores) {
     for (var ruleSet in score.ruleSets!) {
-      if (ruleSet == 'pedantic') {
-        ++pedanticLintCount;
-        if (score.hasFix!) {
-          ++pedanticFixCount;
-        }
-      }
       if (ruleSet == 'flutter') {
         ++flutterUserLintCount;
       }
@@ -83,9 +73,6 @@
   var footer = StringBuffer('\n_${scorecard.lintCount} lints');
 
   var breakdowns = StringBuffer();
-  if (details.contains(Detail.pedantic)) {
-    breakdowns.write('$pedanticLintCount pedantic');
-  }
   if (details.contains(Detail.flutterUser)) {
     if (breakdowns.isNotEmpty) {
       breakdowns.write(', ');
@@ -103,9 +90,6 @@
     breakdowns.write('; ');
   }
   breakdowns.write('$fixCount w/ fixes');
-  if (details.contains(Detail.pedantic)) {
-    breakdowns.write(' ($pedanticFixCount pedantic)');
-  }
 
   if (breakdowns.isNotEmpty) {
     footer.write(': $breakdowns');
@@ -122,8 +106,6 @@
   static const Detail sdk = Detail('dart sdk', header: Header.left);
 
   static const Detail fix = Detail('fix');
-  static const Detail pedantic = Detail('pedantic');
-  static const Detail effectiveDart = Detail('effective_dart');
   static const Detail flutterUser = Detail('flutter user');
   static const Detail flutterRepo = Detail('flutter repo');
   static const Detail status = Detail('status');
@@ -159,7 +141,7 @@
       this.bugReferences,
       this.since});
 
-  String get _ruleSets => ruleSets!.isNotEmpty ? ' ${ruleSets.toString()}' : '';
+  String get _ruleSets => ruleSets!.isNotEmpty ? ' $ruleSets' : '';
 
   String toMarkdown(List<Detail> details) {
     var sb = StringBuffer('| ');
@@ -178,13 +160,6 @@
         case Detail.fix:
           sb.write('${hasFix! ? " $bulb" : ""} |');
           break;
-        case Detail.pedantic:
-          sb.write('${ruleSets!.contains('pedantic') ? " $checkMark" : ""} |');
-          break;
-        case Detail.effectiveDart:
-          sb.write(
-              '${ruleSets!.contains('effective_dart') ? " $checkMark" : ""} |');
-          break;
         case Detail.flutterUser:
           sb.write('${ruleSets!.contains('flutter') ? " $checkMark" : ""} |');
           break;
@@ -246,8 +221,6 @@
     var lintsWithAssists = await _getLintsWithAssists();
     var flutterRuleset = await flutterRules;
     var flutterRepoRuleset = await flutterRepoRules;
-    var pedanticRuleset = await pedanticRules;
-    var effectiveDartRuleset = await effectiveDartRules;
 
     var issues = await getLinterIssues();
     var bugs = issues.where(isBug).toList();
@@ -262,17 +235,12 @@
       if (flutterRepoRuleset.contains(lint.name)) {
         ruleSets.add('flutter_repo');
       }
-      if (pedanticRuleset.contains(lint.name)) {
-        ruleSets.add('pedantic');
-      }
-      if (effectiveDartRuleset.contains(lint.name)) {
-        ruleSets.add('effective_dart');
-      }
+
       var bugReferences = <String>[];
       for (var bug in bugs) {
         var title = bug.title;
         if (title.contains(lint.name)) {
-          bugReferences.add('#${bug.number.toString()}');
+          bugReferences.add('#${bug.number}');
         }
       }
 
@@ -292,11 +260,11 @@
   static Future<List<String>> _getLintsWithAssists() async {
     var client = http.Client();
     var req = await client.get(Uri.parse(
-        'https://raw.githubusercontent.com/dart-lang/sdk/master/pkg/analysis_server/lib/src/services/correction/assist.dart'));
+        'https://raw.githubusercontent.com/dart-lang/sdk/main/pkg/analysis_server/lib/src/services/correction/assist.dart'));
     var parser = CompilationUnitParser();
     var cu = parser.parse(contents: req.body, name: 'assist.dart');
     var assistKindClass = cu.declarations.firstWhere(
-        (m) => m is ClassDeclaration && m.name.name == 'DartAssistKind');
+        (m) => m is ClassDeclaration && m.name.lexeme == 'DartAssistKind');
 
     var collector = _AssistCollector();
     assistKindClass.accept(collector);
@@ -306,12 +274,12 @@
   static Future<List<String>> _getLintsWithFixes() async {
     var client = http.Client();
     var req = await client.get(Uri.parse(
-        'https://raw.githubusercontent.com/dart-lang/sdk/master/pkg/analysis_server/lib/src/services/linter/lint_names.dart'));
+        'https://raw.githubusercontent.com/dart-lang/sdk/main/pkg/analysis_server/lib/src/services/linter/lint_names.dart'));
 
     var parser = CompilationUnitParser();
     var cu = parser.parse(contents: req.body, name: 'lint_names.dart');
-    var lintNamesClass = cu.declarations
-        .firstWhere((m) => m is ClassDeclaration && m.name.name == 'LintNames');
+    var lintNamesClass = cu.declarations.firstWhere(
+        (m) => m is ClassDeclaration && m.name.lexeme == 'LintNames');
 
     var collector = _FixCollector();
     lintNamesClass.accept(collector);
@@ -344,7 +312,7 @@
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
     for (var v in node.fields.variables) {
-      var name = v.name.name;
+      var name = v.name.lexeme;
       lintNames.add(name);
       if (!registeredLintNames.contains(name)) {
         print('WARNING: unrecognized lint in fixes: $name');
diff --git a/tool/since.dart b/tool/since.dart
index 752732f..572abd3 100644
--- a/tool/since.dart
+++ b/tool/since.dart
@@ -12,65 +12,55 @@
 import 'crawl.dart';
 
 void main() async {
-// Uncomment to (re)generate since/linter.yaml contents.
-//  for (var lint in registeredLints) {
-//    var since = await findSinceLinter(lint.name);
-//    if (since != null) {
-//      print('${lint.name}: $since');
-//    }
-//  }
+  // ignore: unused_local_variable
+  var sinceInfo = await getSinceMap();
 
-// Uncomment to (re)generate since/dart_sdk.yaml contents.
-//  var tags = await sdkTags;
-//  for (var tag in sdkTags)) {
-//    var version = await fetchLinterForVersion(tag);
-//    if (version.startsWith('@')) {
-//      version = version.substring(1);
-//    }
-//    print('$tag: $version');
-//  }
+  // Uncomment to (re)generate since/linter.yaml contents.
+  // for (var info in sinceInfo.entries) {
+  //   var sinceLinter = info.value.sinceLinter;
+  //   if (sinceLinter != null) {
+  //     print('${info.key}: $sinceLinter');
+  //   }
+  // }
 
-  await getSinceMap().then((m) => m.entries.forEach(print));
+  // Uncomment to (re)generate since/dart_sdk.yaml contents.
+  // for (var info in sinceInfo.entries) {
+  //   var sinceSdk = info.value.sinceDartSdk;
+  //   if (sinceSdk != null) {
+  //     print('${info.key}: $sinceSdk}');
+  //   }
+  // }
 }
 
-Version earliestLinterInDart2 = Version.parse('0.1.58');
+final Version earliestLinterInDart2 = Version.parse('0.1.58');
 
 Map<String, String>? _dartSdkMap;
 
-List<String>? _linterVersions;
-
 Map<String, SinceInfo>? _sinceMap;
 
-Future<List<String>?> get linterVersions async {
-  if (_linterVersions == null) {
-    _linterVersions = <String>[];
-    for (var minor = 0; minor <= await latestMinor; ++minor) {
-      _linterVersions!.add('0.1.$minor');
-    }
-  }
-  return _linterVersions;
-}
-
-Future<Map<String, String>?> getDartSdkMap(Authentication? auth) async {
-  if (_dartSdkMap == null) {
-    var dartSdkCache = await File('tool/since/dart_sdk.yaml').readAsString();
+Future<Map<String, String>> getDartSdkMap(Authentication? auth) async {
+  var dartSdkMap = _dartSdkMap;
+  if (dartSdkMap == null) {
+    var dartSdkCache = File('tool/since/dart_sdk.yaml').readAsStringSync();
     var yamlMap = loadYamlNode(dartSdkCache) as YamlMap;
-    _dartSdkMap = yamlMap.map((k, v) => MapEntry(k.toString(), v.toString()));
+    dartSdkMap = yamlMap.map((k, v) => MapEntry(k.toString(), v.toString()));
 
-    var sdks = await getSdkTags(auth);
+    var sdks = await getSdkTags(auth, onlyStable: true);
     for (var sdk in sdks) {
-      if (!_dartSdkMap!.containsKey(sdk)) {
+      if (!dartSdkMap.containsKey(sdk)) {
         var linterVersion = await linterForDartSdk(sdk);
         if (linterVersion != null) {
-          _dartSdkMap![sdk] = linterVersion;
+          dartSdkMap[sdk] = linterVersion;
           print('fetched...');
           print('$sdk : $linterVersion');
           print('(consider caching in tool/since/dart_sdk.yaml)');
         }
       }
     }
+
+    _dartSdkMap = dartSdkMap;
   }
-  return _dartSdkMap;
+  return dartSdkMap;
 }
 
 Future<Map<String, SinceInfo>> getSinceMap([Authentication? auth]) async =>
@@ -84,7 +74,7 @@
   for (var lint in registeredLints.map((l) => l.name)) {
     var linterVersion = linterVersionCache[lint] as String?;
     if (linterVersion == null) {
-      linterVersion = await findSinceLinter(lint, auth: auth);
+      linterVersion = await findSinceLinter(lint, auth);
       if (linterVersion != null) {
         print('fetched...');
         print('$lint : $linterVersion');
@@ -92,19 +82,19 @@
       }
     }
     sinceMap[lint] = SinceInfo(
-        sinceLinter: linterVersion ?? await findSinceLinter(lint),
-        sinceDartSdk: await _sinceSdkForLinter(linterVersion, auth));
+      sinceLinter: linterVersion,
+      sinceDartSdk: await _sinceSdkForLinter(linterVersion, auth),
+    );
   }
   return sinceMap;
 }
 
-Future<String?> _nextLinterVersion(Version linterVersion) async {
-  var versions = await linterVersions;
-  if (versions != null) {
-    for (var version in versions) {
-      if (Version.parse(version).compareTo(linterVersion) > 0) {
-        return version;
-      }
+Future<String?> _nextLinterVersion(
+    Version linterVersion, Authentication? auth) async {
+  var versions = await getLinterReleases(auth);
+  for (var version in versions) {
+    if (Version.parse(version) > linterVersion) {
+      return version;
     }
   }
   return null;
@@ -116,32 +106,35 @@
     return null;
   }
 
-  var linterVersion = Version.parse(linterVersionString);
-  if (linterVersion.compareTo(earliestLinterInDart2) < 0) {
-    return bottomDartSdk.toString();
-  }
+  try {
+    var linterVersion = Version.parse(linterVersionString);
+    if (linterVersion.compareTo(earliestLinterInDart2) < 0) {
+      return bottomDartSdk.toString();
+    }
 
-  var sdkVersions = <String>[];
-  var sdkCache = await getDartSdkMap(auth);
-  if (sdkCache != null) {
+    var sdkVersions = <String>[];
+    var sdkCache = await getDartSdkMap(auth);
     for (var sdkEntry in sdkCache.entries) {
       if (Version.parse(sdkEntry.value) == linterVersion) {
         sdkVersions.add(sdkEntry.key);
       }
     }
-  }
-  if (sdkVersions.isEmpty) {
-    var nextLinter = await _nextLinterVersion(linterVersion);
-    return _sinceSdkForLinter(nextLinter, auth);
-  }
+    if (sdkVersions.isEmpty) {
+      var nextLinter = await _nextLinterVersion(linterVersion, auth);
+      return _sinceSdkForLinter(nextLinter, auth);
+    }
 
-  sdkVersions.sort();
-  return sdkVersions.first;
+    sdkVersions.sort();
+    return sdkVersions.first;
+  } on FormatException {
+    return null;
+  }
 }
 
 class SinceInfo {
   final String? sinceLinter;
   final String? sinceDartSdk;
+
   SinceInfo({this.sinceLinter, this.sinceDartSdk});
 
   @override
diff --git a/tool/since/dart_sdk.yaml b/tool/since/dart_sdk.yaml
index 7b6381f..a621e35 100644
--- a/tool/since/dart_sdk.yaml
+++ b/tool/since/dart_sdk.yaml
@@ -1,612 +1,59 @@
-2.12.0-103.0.dev : 0.1.126
-2.12.0-102.0.dev : 0.1.126
-2.12.0-101.0.dev : 0.1.126
-2.12.0-100.0.dev : 0.1.126
-2.12.0-99.0.dev : 0.1.125
-2.12.0-98.0.dev : 0.1.125
-2.12.0-97.0.dev : 0.1.125
-2.12.0-96.0.dev : 0.1.125
-2.12.0-95.0.dev : 0.1.125
-2.12.0-94.0.dev : 0.1.125
-2.12.0-93.0.dev : 0.1.125
-2.12.0-92.0.dev : 0.1.125
-2.12.0-91.0.dev : 0.1.125
-2.12.0-90.0.dev : 0.1.125
-2.12.0-89.0.dev : 0.1.125
-2.12.0-88.0.dev : 0.1.125
-2.12.0-87.0.dev : 0.1.125
-2.12.0-86.0.dev : 0.1.125
-2.12.0-85.0.dev : 0.1.125
-2.12.0-84.0.dev : 0.1.125
-2.12.0-83.0.dev : 0.1.125
-2.12.0-82.0.dev : 0.1.125
-2.12.0-81.0.dev : 0.1.125
-2.12.0-80.0.dev : 0.1.125
-2.12.0-79.0.dev : 0.1.125
-2.12.0-78.0.dev : 0.1.125
-2.12.0-77.0.dev : 0.1.125
-2.12.0-76.0.dev : 0.1.125
-2.12.0-75.0.dev : 0.1.125
-2.12.0-74.0.dev : 0.1.125
-2.12.0-73.0.dev : 0.1.125
-2.12.0-72.0.dev : 0.1.125
-2.12.0-71.0.dev : 0.1.125
-2.12.0-70.0.dev : 0.1.125
-2.12.0-69.0.dev : 0.1.125
-2.12.0-68.0.dev : 0.1.125
-2.12.0-67.0.dev : 0.1.125
-2.12.0-66.0.dev : 0.1.125
-2.12.0-65.0.dev : 0.1.125
-2.12.0-64.0.dev : 0.1.125
-2.12.0-63.0.dev : 0.1.125
-2.12.0-62.0.dev : 0.1.125
-2.12.0-61.0.dev : 0.1.125
-2.12.0-60.0.dev : 0.1.125
-2.12.0-59.0.dev : 0.1.125
-2.12.0-58.0.dev : 0.1.125
-2.12.0-57.0.dev : 0.1.125
-2.12.0-56.0.dev : 0.1.125
-2.12.0-55.0.dev : 0.1.125
-2.12.0-54.0.dev : 0.1.125
-2.12.0-53.0.dev : 0.1.125
-2.12.0-52.0.dev : 0.1.125
-2.12.0-51.0.dev : 0.1.125
-2.12.0-50.0.dev : 0.1.125
-2.12.0-49.0.dev : 0.1.125
-2.12.0-48.0.dev : 0.1.125
-2.12.0-47.0.dev : 0.1.125
-2.12.0-46.0.dev : 0.1.125
-2.12.0-45.0.dev : 0.1.125
-2.12.0-44.0.dev : 0.1.125
-2.12.0-43.0.dev : 0.1.125
-2.12.0-42.0.dev : 0.1.125
-2.12.0-41.0.dev : 0.1.125
-2.12.0-40.0.dev : 0.1.125
-2.12.0-39.0.dev : 0.1.125
-2.12.0-38.0.dev : 0.1.125
-2.12.0-37.0.dev : 0.1.125
-2.12.0-36.0.dev : 0.1.125
-2.12.0-35.0.dev : 0.1.125
-2.12.0-34.0.dev : 0.1.125
-2.12.0-33.0.dev : 0.1.125
-2.12.0-32.0.dev : 0.1.124
-2.12.0-31.0.dev : 0.1.124
-2.12.0-30.0.dev : 0.1.124
-2.12.0-29.10.beta : 0.1.124
-2.12.0-29.7.beta : 0.1.124
-2.12.0-29.1.beta : 0.1.124
-2.12.0-29.0.dev : 0.1.124
-2.12.0-28.0.dev : 0.1.123
-2.12.0-27.0.dev : 0.1.123
-2.12.0-26.0.dev : 0.1.123
-2.12.0-25.0.dev : 0.1.123
-2.12.0-24.0.dev : 0.1.123
-2.12.0-23.0.dev : 0.1.123
-2.12.0-22.0.dev : 0.1.123
-2.12.0-21.0.dev : 0.1.123
-2.12.0-20.0.dev : 0.1.123
-2.12.0-19.0.dev : 0.1.123
-2.12.0-18.0.dev : 0.1.122
-2.12.0-17.0.dev : 0.1.122
-2.12.0-16.0.dev : 0.1.122
-2.12.0-15.0.dev : 0.1.122
-2.12.0-14.0.dev : 0.1.122
-2.12.0-13.0.dev : 0.1.122
-2.12.0-12.0.dev : 0.1.122
-2.10.4 : 0.1.118
-2.12.0-11.0.dev : 0.1.122
-2.12.0-10.0.dev : 0.1.122
-2.12.0-9.0.dev : 0.1.122
-2.12.0-8.0.dev : 0.1.121
-2.12.0-7.0.dev : 0.1.121
-2.12.0-6.0.dev : 0.1.121
-2.12.0-5.0.dev : 0.1.121
-2.12.0-4.0.dev : 0.1.121
-2.12.0-3.0.dev : 0.1.121
-2.12.0-2.0.dev : 0.1.121
-2.12.0-1.0.dev : 0.1.121
-2.12.0-0.0.dev : 0.1.121
-2.11.0-277.0.dev : 0.1.121
-2.11.0-276.0.dev : 0.1.121
-2.11.0-275.0.dev : 0.1.121
-2.11.0-274.0.dev : 0.1.121
-2.11.0-273.0.dev : 0.1.121
-2.11.0-272.0.dev : 0.1.121
-2.11.0-271.0.dev : 0.1.121
-2.11.0-270.0.dev : 0.1.121
-2.11.0-269.0.dev : 0.1.121
-2.11.0-268.0.dev : 0.1.121
-2.11.0-267.0.dev : 0.1.121
-2.11.0-266.0.dev : 0.1.121
-2.11.0-265.0.dev : 0.1.121
-2.11.0-264.0.dev : 0.1.121
-2.11.0-263.0.dev : 0.1.121
-2.11.0-262.0.dev : 0.1.121
-2.11.0-261.0.dev : 0.1.121
-2.11.0-260.0.dev : 0.1.121
-2.11.0-259.0.dev : 0.1.121
-2.11.0-258.0.dev : 0.1.121
-2.11.0-257.0.dev : 0.1.121
-2.11.0-256.0.dev : 0.1.121
-2.11.0-255.0.dev : 0.1.121
-2.11.0-254.0.dev : 0.1.121
-2.11.0-253.0.dev : 0.1.121
-2.11.0-252.0.dev : 0.1.121
-2.11.0-251.0.dev : 0.1.121
-2.11.0-250.0.dev : 0.1.121
-2.11.0-249.0.dev : 0.1.121
-2.11.0-248.0.dev : 0.1.121
-2.11.0-247.0.dev : 0.1.121
-2.11.0-246.0.dev : 0.1.121
-2.11.0-245.0.dev : 0.1.121
-2.11.0-244.0.dev : 0.1.121
-2.11.0-243.0.dev : 0.1.121
-2.11.0-242.0.dev : 0.1.121
-2.11.0-241.0.dev : 0.1.121
-2.11.0-240.0.dev : 0.1.121
-2.11.0-239.0.dev : 0.1.121
-2.11.0-238.0.dev : 0.1.121
-2.11.0-237.0.dev : 0.1.121
-2.11.0-236.0.dev : 0.1.121
-2.11.0-235.0.dev : 0.1.121
-2.11.0-234.0.dev : 0.1.121
-2.11.0-233.0.dev : 0.1.121
-2.11.0-232.0.dev : 0.1.121
-2.11.0-231.0.dev : 0.1.121
-2.11.0-230.0.dev : 0.1.121
-2.11.0-229.0.dev : 0.1.121
-2.11.0-228.0.dev : 0.1.121
-2.11.0-227.0.dev : 0.1.121
-2.11.0-226.0.dev : 0.1.121
-2.11.0-225.0.dev : 0.1.121
-2.11.0-224.0.dev : 0.1.121
-2.11.0-223.0.dev : 0.1.121
-2.11.0-222.0.dev : 0.1.121
-2.11.0-221.0.dev : 0.1.121
-2.11.0-219.0.dev : 0.1.121
-2.11.0-218.0.dev : 0.1.121
-2.11.0-217.0.dev : 0.1.121
-2.11.0-216.0.dev : 0.1.121
-2.11.0-215.0.dev : 0.1.121
-2.11.0-214.0.dev : 0.1.121
-2.11.0-213.5.beta : 0.1.121
-2.11.0-213.4.beta : 0.1.121
-2.11.0-213.1.beta : 0.1.121
-2.11.0-213.0.dev : 0.1.121
-2.11.0-212.0.dev : 0.1.121
-2.11.0-211.0.dev : 0.1.121
-2.11.0-210.0.dev : 0.1.121
-2.11.0-209.0.dev : 0.1.121
-2.11.0-208.0.dev : 0.1.121
-2.11.0-207.0.dev : 0.1.121
-2.11.0-206.0.dev : 0.1.121
-2.11.0-205.0.dev : 0.1.121
-2.11.0-204.0.dev : 0.1.121
-2.11.0-203.0.dev : 0.1.120
-2.11.0-202.0.dev : 0.1.120
-2.11.0-201.0.dev : 0.1.120
-2.11.0-200.0.dev : 0.1.120
-2.11.0-199.0.dev : 0.1.120
-2.11.0-198.0.dev : 0.1.120
-2.11.0-197.0.dev : 0.1.120
-2.11.0-196.0.dev : 0.1.120
-2.11.0-195.0.dev : 0.1.120
-2.11.0-194.0.dev : 0.1.120
-2.11.0-193.0.dev : 0.1.120
-2.11.0-192.0.dev : 0.1.120
-2.11.0-191.0.dev : 0.1.120
-2.11.0-190.0.dev : 0.1.120
-2.11.0-189.0.dev : 0.1.120
-2.11.0-188.0.dev : 0.1.120
-2.11.0-187.0.dev : 0.1.120
-2.11.0-186.0.dev : 0.1.120
-2.11.0-185.0.dev : 0.1.120
-2.11.0-184.0.dev : 0.1.120
-2.11.0-183.0.dev : 0.1.120
-2.11.0-182.0.dev : 0.1.120
-2.10.3 : 0.1.118
-2.10.2 : 0.1.118
-2.10.1 : 0.1.118
-2.11.0-181.0.dev : 0.1.119
-2.11.0-180.0.dev : 0.1.119
-2.11.0-176.0.dev : 0.1.119
-2.11.0-163.0.dev : 0.1.119
-2.11.0-162.0.dev : 0.1.119
-2.11.0-161.0.dev : 0.1.119
-2.11.0-160.0.dev : 0.1.119
-2.11.0-159.0.dev : 0.1.119
-2.11.0-158.0.dev : 0.1.119
-2.11.0-157.0.dev : 0.1.119
-2.10.0 : 0.1.118
-2.11.0-155.0.dev : 0.1.119
-2.11.0-154.0.dev : 0.1.119
-2.11.0-153.0.dev : 0.1.119
-2.10.0-156.0.dev : 0.1.119
-2.10.0-152.0.dev : 0.1.119
-2.10.0-151.0.dev : 0.1.119
-2.10.0-150.0.dev : 0.1.119
-2.10.0-149.0.dev : 0.1.119
-2.10.0-148.0.dev : 0.1.119
-2.10.0-147.0.dev : 0.1.119
-2.10.0-146.0.dev : 0.1.119
-2.10.0-145.0.dev : 0.1.119
-2.10.0-144.0.dev : 0.1.119
-2.10.0-143.0.dev : 0.1.118
-2.10.0-142.0.dev : 0.1.118
-2.10.0-141.0.dev : 0.1.118
-2.10.0-140.0.dev : 0.1.118
-2.10.0-139.0.dev : 0.1.118
-2.10.0-138.0.dev : 0.1.118
-2.10.0-137.0.dev : 0.1.118
-2.10.0-136.0.dev : 0.1.118
-2.10.0-135.0.dev : 0.1.118
-2.10.0-134.0.dev : 0.1.118
-2.10.0-133.0.dev : 0.1.118
-2.10.0-132.0.dev : 0.1.118
-2.10.0-131.0.dev : 0.1.118
-2.10.0-130.0.dev : 0.1.118
-2.10.0-129.0.dev : 0.1.118
-2.10.0-128.0.dev : 0.1.118
-2.10.0-127.0.dev : 0.1.118
-2.10.0-126.0.dev : 0.1.118
-2.10.0-125.0.dev : 0.1.118
-2.10.0-124.0.dev : 0.1.118
-2.10.0-123.0.dev : 0.1.118
-2.10.0-122.0.dev : 0.1.118
-2.10.0-121.0.dev : 0.1.118
-2.10.0-120.0.dev : 0.1.118
-2.10.0-119.0.dev : 0.1.118
-2.10.0-118.0.dev : 0.1.118
-2.10.0-117.0.dev : 0.1.118
-2.10.0-116.0.dev : 0.1.118
-2.10.0-115.0.dev : 0.1.118
-2.10.0-114.0.dev : 0.1.118
-2.10.0-113.0.dev : 0.1.118
-2.10.0-112.0.dev : 0.1.118
-2.10.0-111.0.dev : 0.1.118
-2.10.0-110.5.beta : 0.1.118
-2.10.0-110.3.beta : 0.1.118
-2.10.0-110.1.beta : 0.1.118
-2.10.0-110.0.dev : 0.1.118
-2.10.0-109.0.dev : 0.1.118
-2.10.0-108.0.dev : 0.1.118
-2.10.0-107.0.dev : 0.1.118
-2.10.0-106.0.dev : 0.1.118
-2.10.0-105.0.dev : 0.1.118
-2.10.0-104.0.dev : 0.1.118
-2.10.0-103.0.dev : 0.1.118
-2.10.0-102.0.dev : 0.1.118
-2.10.0-101.0.dev : 0.1.118
-2.10.0-100.0.dev : 0.1.118
-2.10.0-99.0.dev : 0.1.118
-2.10.0-98.0.dev : 0.1.118
-2.10.0-97.0.dev : 0.1.118
-2.10.0-96.0.dev : 0.1.118
-2.10.0-95.0.dev : 0.1.118
-2.10.0-94.0.dev : 0.1.118
-2.10.0-93.0.dev : 0.1.118
-2.10.0-92.0.dev : 0.1.118
-2.10.0-91.0.dev : 0.1.118
-2.10.0-90.0.dev : 0.1.118
-2.9.3 : 0.1.117
-2.14.0-0.0.dev : 0.1.117
-2.10.0-89.0.dev : 0.1.118
-2.10.0-88.0.dev : 0.1.118
-2.10.0-87.0.dev : 0.1.118
-2.10.0-86.0.dev : 0.1.118
-2.10.0-85.0.dev : 0.1.118
-2.10.0-84.0.dev : 0.1.118
-2.10.0-83.0.dev : 0.1.118
-2.10.0-82.0.dev : 0.1.118
-2.10.0-81.0.dev : 0.1.118
-2.10.0-80.0.dev : 0.1.118
-2.10.0-79.0.dev : 0.1.118
-2.10.0-78.0.dev : 0.1.118
-2.10.0-77.0.dev : 0.1.118
-2.10.0-76.0.dev : 0.1.118
-2.10.0-75.0.dev : 0.1.118
-2.10.0-74.0.dev : 0.1.118
-2.10.0-73.0.dev : 0.1.118
-2.10.0-72.0.dev : 0.1.118
-2.10.0-71.0.dev : 0.1.118
-2.10.0-70.0.dev : 0.1.118
-2.10.0-69.0.dev : 0.1.118
-2.10.0-68.0.dev : 0.1.118
-2.10.0-67.0.dev : 0.1.118
-2.10.0-66.0.dev : 0.1.118
-2.10.0-65.0.dev : 0.1.118
-2.10.0-64.0.dev : 0.1.118
-2.10.0-63.0.dev : 0.1.118
-2.10.0-62.0.dev : 0.1.118
-2.10.0-61.0.dev : 0.1.118
-2.10.0-60.0.dev : 0.1.118
-2.10.0-59.0.dev : 0.1.118
-2.10.0-58.0.dev : 0.1.118
-2.10.0-57.0.dev : 0.1.118
-2.10.0-56.0.dev : 0.1.118
-2.10.0-55.0.dev : 0.1.118
-2.10.0-54.0.dev : 0.1.118
-2.10.0-53.0.dev : 0.1.118
-2.10.0-52.0.dev : 0.1.118
-2.10.0-51.0.dev : 0.1.118
-2.10.0-50.0.dev : 0.1.118
-2.10.0-49.0.dev : 0.1.118
-2.10.0-48.0.dev : 0.1.118
-2.10.0-47.0.dev : 0.1.118
-2.10.0-46.0.dev : 0.1.118
-2.10.0-45.0.dev : 0.1.118
-2.10.0-44.0.dev : 0.1.118
-2.10.0-43.0.dev : 0.1.118
-2.10.0-42.0.dev : 0.1.118
-2.10.0-41.0.dev : 0.1.118
-2.10.0-40.0.dev : 0.1.118
-2.10.0-39.0.dev : 0.1.118
-2.10.0-38.0.dev : 0.1.118
-2.10.0-37.0.dev : 0.1.118
-2.10.0-36.0.dev : 0.1.118
-2.10.0-35.0.dev : 0.1.118
-2.10.0-34.0.dev : 0.1.118
-2.10.0-33.0.dev : 0.1.118
-2.10.0-32.0.dev : 0.1.118
-2.10.0-31.0.dev : 0.1.118
-2.10.0-30.0.dev : 0.1.118
-2.10.0-29.0.dev : 0.1.118
-2.10.0-28.0.dev : 0.1.118
-2.10.0-27.0.dev : 0.1.118
-2.10.0-26.0.dev : 0.1.118
-2.10.0-25.0.dev : 0.1.118
-2.10.0-24.0.dev : 0.1.118
-2.10.0-23.0.dev : 0.1.118
-2.10.0-22.0.dev : 0.1.118
-2.10.0-21.0.dev : 0.1.118
-2.10.0-20.0.dev : 0.1.118
-2.10.0-19.0.dev : 0.1.118
-2.10.0-18.0.dev : 0.1.118
-2.10.0-17.0.dev : 0.1.118
-2.10.0-16.0.dev : 0.1.118
-2.10.0-15.0.dev : 0.1.118
-2.10.0-14.0.dev : 0.1.118
-2.10.0-13.0.dev : 0.1.118
-2.10.0-12.0.dev : 0.1.118
-2.10.0-11.0.dev : 0.1.118
-2.10.0-10.0.dev : 0.1.118
-2.10.0-9.0.dev : 0.1.118
-2.10.0-8.0.dev : 0.1.118
-2.10.0-7.3.beta : 0.1.118
-2.10.0-7.2.beta : 0.1.118
-2.10.0-7.1.beta : 0.1.118
-2.10.0-7.0.dev : 0.1.118
-2.10.0-6.0.dev : 0.1.118
-2.10.0-5.0.dev : 0.1.118
-2.10.0-4.0.dev : 0.1.117
-2.10.0-3.0.dev : 0.1.117
-2.10.0-2.0.dev : 0.1.117
-2.10.0-1.0.dev : 0.1.117
-2.10.0-0.0.dev : 0.1.117
-2.9.2 : 0.1.117
-2.9.1 : 0.1.117
-2.9.0 : 0.1.117
-2.9.0-21.10.beta : 0.1.117
-2.9.0-21.4.beta : 0.1.117
-2.9.0-21.2.beta : 0.1.117
-2.9.0-21.1.beta : 0.1.117
-2.9.0-21.0.dev : 0.1.117
-2.9.0-20.0.dev : 0.1.117
-2.9.0-19.0.dev : 0.1.117
-2.9.0-18.0.dev : 0.1.117
-2.9.0-17.0.dev : 0.1.117
-2.9.0-16.0.dev : 0.1.117
-2.9.0-15.0.dev : 0.1.116
-2.9.0-14.1.beta : 0.1.116
-2.9.0-14.0.dev : 0.1.116
-2.9.0-13.0.dev : 0.1.116
-2.9.0-12.0.dev : 0.1.116
-2.9.0-11.0.dev : 0.1.115
-2.9.0-10.0.dev : 0.1.115
-2.9.0-9.0.dev : 0.1.115
-2.9.0-8.2.beta : 0.1.115
-2.9.0-8.1.beta : 0.1.115
-2.9.0-8.0.dev : 0.1.115
-2.9.0-7.0.dev : 0.1.115
-2.8.4 : 0.1.114
-2.8.3 : 0.1.114
-2.8.2 : 0.1.114
-2.8.1 : 0.1.114
-2.9.0-5.0.dev : 0.1.115
-2.9.0-4.0.dev : 0.1.115
-2.9.0-3.0.dev : 0.1.114
-2.9.0-2.0.dev : 0.1.114
-2.9.0-1.0.dev : 0.1.114
-2.8.0-dev.20.7 : 0.1.114
-2.8.0-20.11.beta : 0.1.114
-2.8.0-dev.20.0 : 0.1.114
-2.8.0-dev.19.0 : 0.1.114
-2.8.0-dev.18.0 : 0.1.114
-2.8.0-dev.17.0 : 0.1.113
-2.8.0-dev.16.0 : 0.1.113
-2.8.0-dev.15.0 : 0.1.112
-2.7.2 : 0.1.104
-2.8.0-dev.14.0 : 0.1.112
-2.8.0-dev.13.0 : 0.1.112
-2.8.0-dev.12.0 : 0.1.112
-2.8.0-dev.11.0 : 0.1.112
-2.8.0-dev.10.0 : 0.1.110
-2.8.0-dev.9.0 : 0.1.110
-2.8.0-dev.8.0 : 0.1.110
-2.8.0-dev.7.0 : 0.1.110
-2.8.0-dev.6.0 : 0.1.109
-2.8.0-dev.5.0 : 0.1.109
-2.8.0-dev.4.0 : 0.1.109
-2.7.1 : 0.1.104
-2.8.0-dev.3.0 : 0.1.108
-2.8.0-dev.2.0 : 0.1.108
-2.8.0-dev.1.0 : 0.1.108
-2.8.0-dev.0.0 : 0.1.105+1
-2.7.0 : 0.1.104
-2.7.0-dev.2.1 : 0.1.104
-2.7.0-dev.2.0 : 0.1.104
-2.7.0-dev.1.0 : 0.1.103
-2.7.0-dev.0.0 : 0.1.103
-2.6.1 : 0.1.101
-2.6.0 : 0.1.101
-2.6.0-dev.8.2 : 0.1.101
-2.6.0-dev.8.1 : 0.1.101
-2.6.0-dev.8.0 : 0.1.101
-2.6.0-dev.7.0 : 0.1.100
-2.6.0-dev.6.0 : 0.1.100
-2.6.0-dev.5.0 : 0.1.99
-2.6.0-dev.4.0 : 0.1.98
-2.6.0-dev.3.0 : 0.1.98
-2.6.0-dev.2.0 : 0.1.97+1
-2.6.0-dev.1.0 : 0.1.97+1
-2.6.0-dev.0.0 : 0.1.97+1
-2.5.2 : 0.1.96
-2.5.1 : 0.1.96
-2.5.0 : 0.1.96
-2.5.0-dev.4.0 : 0.1.96
-2.5.0-dev.3.0 : 0.1.96
-2.5.0-dev.2.1 : 0.1.96
-2.5.0-dev.2.0 : 0.1.96
-2.5.0-dev.1.0 : 0.1.93
-2.5.0-dev.0.0 : 0.1.91
-2.4.1 : 0.1.91
-2.4.0 : 0.1.91
-2.4.0-dev.0.1 : 0.1.91
-2.4.0-dev.0.0 : 0.1.91
-2.3.3-dev.0.0 : 0.1.91
-2.3.2 : 0.1.86
-2.3.2-dev.0.1 : 0.1.90
-2.3.2-dev.0.0 : 0.1.89
-2.3.1 : 0.1.86
-2.3.1-dev.0.0 : 0.1.87
-2.3.0 : 0.1.86
-2.3.0-flutter-1.5.4-hotfix.1 : 0.1.86
-2.3.0-dev.0.5 : 0.1.86
-2.3.0-dev.0.4 : 0.1.86
-2.3.0-dev.0.3 : 0.1.86
-2.3.0-dev.0.2 : 0.1.86
-2.3.0-dev.0.1 : 0.1.86
-2.3.0-dev.0.0 : 0.1.86
-2.2.1-dev.4.2 : 0.1.85
-2.2.1-dev.4.1 : 0.1.85
-2.2.1-dev.4.0 : 0.1.85
-2.2.1-dev.3.1 : 0.1.83
-2.2.1-dev.3.0 : 0.1.83
-2.2.1-dev.2.1 : 0.1.82
-2.2.1-dev.2.0 : 0.1.82
-2.2.1-dev.1.1 : 0.1.82
-2.2.1-dev.1.0 : 0.1.82
-2.2.1-dev.0.0 : 0.1.82
-2.2.0 : 0.1.82
-2.2.0-dev.2.1 : 0.1.82
-2.2.0-dev.2.0 : 0.1.82
-2.1.1 : 0.1.78
-2.1.2-dev.0.0 : 0.1.79
-2.1.1-dev.3.2 : 0.1.78
-2.1.1-dev.3.1 : 0.1.78
-2.1.1-dev.3.0 : 0.1.78
-2.1.1-dev.2.0 : 0.1.78
-2.1.1-dev.1.0 : 0.1.78
-2.2.0-dev.1.1: 0.1.73
-2.2.0-dev.1.0: 0.1.73
-2.2.0-dev.0.0: 0.1.71
-2.1.1-dev.0.1: 0.1.73
-2.1.1-dev.0.0: 0.1.73
+2.18.2: 1.25.0
+2.18.1: 1.25.0
+2.18.0: 1.25.0
+2.17.7: 1.22.0
+2.17.6: 1.22.0
+2.17.5: 1.22.0
+2.17.3: 1.22.0
+2.17.1: 1.22.0
+2.17.0: 1.22.0
+2.16.2: 1.17.1
+2.16.1: 1.17.1
+2.16.0: 1.17.1
+2.15.1: 1.14.0
+2.15.0: 1.14.0
+2.14.4: 1.8.0
+2.14.3: 1.8.0
+2.14.2: 1.8.0
+2.14.1: 1.8.0
+2.14.0: 1.8.0
+2.13.4: 1.2.1
+2.13.3: 1.2.1
+2.13.1: 1.2.1
+2.13.0: 1.2.1
+2.12.4: 0.1.129
+2.12.3: 0.1.129
+2.12.2: 0.1.129
+2.12.1: 0.1.129
+2.12.0: 0.1.129
+2.10.5: 0.1.118
+2.10.4: 0.1.118
+2.10.3: 0.1.118
+2.10.2: 0.1.118
+2.10.1: 0.1.118
+2.10.0: 0.1.118
+2.9.3: 0.1.117
+2.9.2: 0.1.117
+2.9.1: 0.1.117
+2.9.0: 0.1.117
+2.8.4: 0.1.114
+2.8.3: 0.1.114
+2.8.2: 0.1.114
+2.8.1: 0.1.114
+2.7.2: 0.1.104
+2.7.1: 0.1.104
+2.7.0: 0.1.104
+2.6.1: 0.1.101
+2.6.0: 0.1.101
+2.5.2: 0.1.96
+2.5.1: 0.1.96
+2.5.0: 0.1.96
+2.4.1: 0.1.91
+2.4.0: 0.1.91
+2.3.2: 0.1.86
+2.3.1: 0.1.86
+2.3.0: 0.1.86
+2.2.0: 0.1.82
+2.1.1: 0.1.78
 2.1.0: 0.1.71
-2.1.0-dev.9.4: 0.1.71
-2.1.0-dev.9.3: 0.1.71
-2.1.0-dev.9.2: 0.1.71
-2.1.0-dev.9.1: 0.1.71
-2.1.0-dev.9.0: 0.1.70
-2.1.0-dev.8.0: 0.1.70
-2.1.0-dev.7.1: 0.1.68
-2.1.0-dev.7.0: 0.1.68
-2.1.0-dev.6.0: 0.1.63
-2.1.0-dev.5.0: 0.1.62
-2.1.0-dev.4.0: 0.1.60
-2.1.0-dev.3.1: 0.1.60
-2.1.0-dev.3.0: 0.1.60
-2.1.0-dev.2.0: 0.1.59
-2.1.0-dev.1.0: 0.1.59
-2.1.0-dev.0.0: 0.1.58
 2.0.0: 0.1.58
-
-# Uncomment if 2.0.0-dev granularity is of interest.
-#2.0.0-dev.69.5: 0.1.58
-#2.0.0-dev.69.4: 0.1.58
-#2.0.0-dev.69.3: 0.1.58
-#2.0.0-dev.69.2: 0.1.58
-#2.0.0-dev.69.1: 0.1.58
-#2.0.0-dev.69.0: 0.1.56
-#2.0.0-dev.68.0: 0.1.56
-#2.0.0-dev.67.0: 0.1.56
-#2.0.0-dev.66.0: 0.1.54
-#2.0.0-dev.65.0: 0.1.54
-#2.0.0-dev.64.2: 0.1.54
-#2.0.0-dev.64.1: 0.1.54
-#2.0.0-dev.64.0: 0.1.54
-#2.0.0-dev.63.0: 0.1.53
-#2.0.0-dev.62.0: 0.1.52
-#2.0.0-dev.61.0: 0.1.52
-#2.0.0-dev.60.0: 0.1.51
-#2.0.0-dev.59.0: 0.1.51
-#2.0.0-dev.58.0: 0.1.51
-#2.0.0-dev.57.0: 0.1.51
-#2.0.0-dev.56.0: 0.1.51
-#2.0.0-dev.55.0: 0.1.50
-#2.0.0-dev.54.0: 0.1.49
-#2.0.0-dev.53.0: 0.1.49
-#2.0.0-dev.52.0: 0.1.49
-#2.0.0-dev.51.0: 0.1.49
-#2.0.0-dev.50.0: 0.1.49
-#2.0.0-dev.49.0: 0.1.48
-#2.0.0-dev.48.0: 0.1.47
-#2.0.0-dev.47.0: 0.1.47
-#2.0.0-dev.46.0: 0.1.46
-#2.0.0-dev.45.0: 0.1.46
-#2.0.0-dev.44.0: 0.1.46
-#2.0.0-dev.43.0: 0.1.46
-#2.0.0-dev.42.0: 0.1.45
-#2.0.0-dev.41.0: 0.1.45
-#2.0.0-dev.40.0: 0.1.44
-#2.0.0-dev.39.0: 0.1.44
-#2.0.0-dev.38.0: 0.1.44
-#2.0.0-dev.37.0: 0.1.44
-#2.0.0-dev.36.0: 0.1.43
-#2.0.0-dev.35: 0.1.43
-#2.0.0-dev.34.0: 0.1.43
-#2.0.0-dev.33.0: 0.1.43
-#2.0.0-dev.32.0: 0.1.43
-#2.0.0-dev.31.0: 0.1.43
-#2.0.0-dev.30.0: 0.1.43
-#2.0.0-dev.29.0: 0.1.43
-#2.0.0-dev.28.0: 0.1.43
-#2.0.0-dev.27.0: 0.1.43
-#2.0.0-dev.26.0: 0.1.43
-#2.0.0-dev.25.0: 0.1.43
-#2.0.0-dev.24.0: 0.1.43
-#2.0.0-dev.23.0: 0.1.43
-#2.0.0-dev.22.0: 0.1.43
-#2.0.0-dev.21.0: 0.1.42
-#2.0.0-dev.20.0: 0.1.42
-#2.0.0-dev.19.0: 0.1.42
-#2.0.0-dev.18.0: 0.1.42
-#2.0.0-dev.17.0: 0.1.41
-#2.0.0-dev.16.0: 0.1.41
-#2.0.0-dev.15.0: 0.1.41
-#2.0.0-dev.14.0: 0.1.41
-#2.0.0-dev.13.0: 0.1.41
-#2.0.0-dev.12.0: 0.1.40
-#2.0.0-dev.11.0: 0.1.40
-#2.0.0-dev.10.0: 0.1.40
-#2.0.0-dev.9.0: 0.1.39
-#2.0.0-dev.8.0: 0.1.39
-#2.0.0-dev.7.0: 0.1.39
-#2.0.0-dev.6.0: 0.1.38
-#2.0.0-dev.5.0: 0.1.37
-#2.0.0-dev.4.0: 0.1.35
-#2.0.0-dev.3.0: 0.1.35
-#2.0.0-dev.2.0: 0.1.35
-#2.0.0-dev.1.0: 0.1.35
-#2.0.0-dev.0.1: 0.1.35
-#2.0.0-dev.0.0: 0.1.35
diff --git a/tool/since/linter.yaml b/tool/since/linter.yaml
index 097718c..0eaa625 100644
--- a/tool/since/linter.yaml
+++ b/tool/since/linter.yaml
@@ -3,64 +3,73 @@
 always_put_required_named_parameters_first: 0.1.33
 always_require_non_null_named_parameters: 0.1.31
 always_specify_types: 0.1.4
+always_use_package_imports: 0.1.118
 annotate_overrides: 0.1.11
 avoid_annotating_with_dynamic: 0.1.31
-avoid_bool_literals_in_conditional_expressions: 0.1.46
-avoid_type_to_string : 0.1.119
-avoid_types_on_closure_parameters: 0.1.31
 avoid_as: 0.1.5
-avoid_catching_errors: 0.1.31
+avoid_bool_literals_in_conditional_expressions: 0.1.46
 avoid_catches_without_on_clauses: 0.1.31
+avoid_catching_errors: 0.1.31
 avoid_classes_with_only_static_members: 0.1.31
 avoid_double_and_int_checks: 0.1.47
+avoid_dynamic_calls: 0.1.128
 avoid_empty_else: 0.1.8
-avoid_equals_and_hash_code_on_mutable_classes : 0.1.97
 avoid_escaping_inner_quotes: 0.1.111
 avoid_field_initializers_in_const_classes: 0.1.48
+avoid_final_parameters: 1.15.0
 avoid_function_literals_in_foreach_calls: 0.1.30
 avoid_implementing_value_types: 0.1.62
 avoid_init_to_null: 0.1.11
 avoid_js_rounded_ints: 0.1.48
+avoid_multiple_declarations_per_line: 1.1.0
 avoid_null_checks_in_equality_operators: 0.1.31
+avoid_equals_and_hash_code_on_mutable_classes: 0.1.97
 avoid_positional_boolean_parameters: 0.1.31
 avoid_print: 0.1.93
 avoid_private_typedef_functions: 0.1.46
-avoid_redundant_argument_values : 0.1.107
+avoid_redundant_argument_values: 0.1.107
 avoid_relative_lib_imports: 0.1.44
 avoid_renaming_method_parameters: 0.1.45
 avoid_returning_null: 0.1.31
 avoid_returning_null_for_future: 0.1.72
 avoid_returning_null_for_void: 0.1.69
-avoid_return_types_on_setters: 0.1.11
 avoid_returning_this: 0.1.31
+avoid_return_types_on_setters: 0.1.11
 avoid_setters_without_getters: 0.1.31
 avoid_shadowing_type_parameters: 0.1.72
 avoid_single_cascade_in_expression_statements: 0.1.46
 avoid_slow_async_io: 0.1.30
+avoid_type_to_string: 0.1.119
 avoid_types_as_parameter_names: 0.1.45
-avoid_unnecessary_containers : 0.1.102
+avoid_types_on_closure_parameters: 0.1.31
+avoid_unnecessary_containers: 0.1.102
 avoid_unused_constructor_parameters: 0.1.36
-always_use_package_imports : 0.1.118
 avoid_void_async: 0.1.60
-avoid_web_libraries_in_flutter : 0.1.101
+avoid_web_libraries_in_flutter: 0.1.101
 await_only_futures: 0.1.16
-camel_case_types: 0.1.1
 camel_case_extensions: 0.1.97+1
+camel_case_types: 0.1.1
 cancel_subscriptions: 0.1.20
 cascade_invocations: 0.1.29
-cast_nullable_to_non_nullable : 0.1.120
+cast_nullable_to_non_nullable: 0.1.120
 close_sinks: 0.1.19
+combinators_ordering: 1.26.0
 comment_references: 0.1.17
-control_flow_in_finally: 0.1.16
+conditional_uri_does_not_exist: 1.16.0
 constant_identifier_names: 0.1.1
+control_flow_in_finally: 0.1.16
 curly_braces_in_flow_control_structures: 0.1.57
+depend_on_referenced_packages: 1.5.0
+deprecated_consistency: 1.0.0
 diagnostic_describe_all_properties: 0.1.85
 directives_ordering: 0.1.30
-do_not_use_environment : 0.1.117
+discarded_futures: 1.25.0
+do_not_use_environment: 0.1.117
 empty_catches: 0.1.22
 empty_constructor_bodies: 0.1.1
 empty_statements: 0.1.21
-exhaustive_cases : 0.1.116
+eol_at_end_of_file: 1.8.0
+exhaustive_cases: 0.1.116
 file_names: 0.1.54
 flutter_style_todos: 0.1.61
 hash_and_equals: 0.1.11
@@ -68,29 +77,34 @@
 invariant_booleans: 0.1.25
 iterable_contains_unrelated_type: 0.1.17
 join_return_with_assignment: 0.1.31
-leading_newlines_in_multiline_strings : 0.1.113
+leading_newlines_in_multiline_strings: 0.1.113
 library_names: 0.1.1
 library_prefixes: 0.1.1
+library_private_types_in_public_api: 1.3.0
 lines_longer_than_80_chars: 0.1.56
 list_remove_unrelated_type: 0.1.22
 literal_only_boolean_expressions: 0.1.25
-missing_whitespace_between_adjacent_strings : 0.1.110
+missing_whitespace_between_adjacent_strings: 0.1.110
 no_adjacent_strings_in_list: 0.1.30
-no_default_cases : 0.1.116
+no_default_cases: 0.1.116
 no_duplicate_case_values: 0.1.30
-no_logic_in_create_state : 0.1.106
 non_constant_identifier_names: 0.1.1
-no_runtimeType_toString : 0.1.110
-null_check_on_nullable_type_parameter : 0.1.120
+no_leading_underscores_for_library_prefixes: 1.15.0
+no_leading_underscores_for_local_identifiers: 1.15.0
+no_logic_in_create_state: 0.1.106
+noop_primitive_operations: 1.5.0
+no_runtimeType_toString: 0.1.110
+null_check_on_nullable_type_parameter: 0.1.120
 null_closures: 0.1.56
-one_member_abstracts: 0.1.1
 omit_local_variable_types: 0.1.30
+one_member_abstracts: 0.1.1
 only_throw_errors: 0.1.21
 overridden_fields: 0.1.18
 package_api_docs: 0.1.1
 package_prefixed_library_names: 0.1.1
 parameter_assignments: 0.1.27
 prefer_adjacent_string_concatenation: 0.1.30
+prefer_asserts_in_initializer_lists: 0.1.33
 prefer_asserts_with_message: 0.1.84
 prefer_bool_in_asserts: 0.1.36
 prefer_collection_literals: 0.1.30
@@ -99,7 +113,6 @@
 prefer_const_constructors_in_immutables: 0.1.33
 prefer_const_declarations: 0.1.43
 prefer_const_literals_to_create_immutables: 0.1.43
-prefer_asserts_in_initializer_lists: 0.1.33
 prefer_constructors_over_static_methods: 0.1.31
 prefer_contains: 0.1.30
 prefer_double_quotes: 0.1.88
@@ -108,6 +121,7 @@
 prefer_final_fields: 0.1.27
 prefer_final_in_for_each: 0.1.78
 prefer_final_locals: 0.1.27
+prefer_final_parameters: 1.5.0
 prefer_foreach: 0.1.31
 prefer_for_elements_to_map_fromIterable: 0.1.85
 prefer_function_declarations_over_variables: 0.1.30
@@ -116,15 +130,16 @@
 prefer_if_null_operators: 0.1.89
 prefer_initializing_formals: 0.1.30
 prefer_inlined_adds: 0.1.86
-prefer_int_literals: 0.1.71
 prefer_interpolation_to_compose_strings: 0.1.30
-prefer_iterable_whereType: 0.1.47
+prefer_int_literals: 0.1.71
 prefer_is_empty: 0.1.30
 prefer_is_not_empty: 0.1.5
-prefer_is_not_operator : 0.1.102
+prefer_is_not_operator: 0.1.102
+prefer_iterable_whereType: 0.1.47
 prefer_mixin: 0.1.62
 prefer_null_aware_operators: 0.1.80
-prefer_relative_imports : 0.1.99
+prefer_null_aware_method_calls: 1.3.0
+prefer_relative_imports: 0.1.99
 prefer_single_quotes: 0.1.33
 prefer_spread_collections: 0.1.85
 prefer_typing_uninitialized_variables: 0.1.36
@@ -132,8 +147,11 @@
 provide_deprecation_message: 0.1.82
 public_member_api_docs: 0.1.11
 package_names: 0.1.31
+secure_pubspec_urls: 1.15.0
 recursive_getters: 0.1.30
-sized_box_for_whitespace : 0.1.115
+require_trailing_commas: 1.3.0
+sized_box_for_whitespace: 0.1.115
+sized_box_shrink_expand: 1.15.0
 slash_for_doc_comments: 0.1.1
 sort_child_properties_last: 0.1.88
 sort_constructors_first: 0.1.11
@@ -141,40 +159,54 @@
 sort_unnamed_constructors_first: 0.1.11
 super_goes_last: 0.1.1
 test_types_in_equals: 0.1.16
+tighten_type_of_initializing_formals: 0.1.120
 throw_in_finally: 0.1.16
-tighten_type_of_initializing_formals : 0.1.120
 type_annotate_public_apis: 0.1.5
 type_init_formals: 0.1.1
 unawaited_futures: 0.1.19
 unnecessary_await_in_return: 0.1.73
 unnecessary_brace_in_string_interps: 0.1.30
 unnecessary_const: 0.1.54
-unnecessary_final : 0.1.104
-unnecessary_getters_setters: 0.1.1
-unnecessary_lambdas: 0.1.30
+unnecessary_constructor_name: 1.11.0
+unnecessary_final: 0.1.104
 unnecessary_new: 0.1.54
 unnecessary_null_aware_assignments: 0.1.30
-unnecessary_null_checks : 0.1.119
-unnecessary_nullable_for_final_variable_declarations : 0.1.118
 unnecessary_null_in_if_null_operators: 0.1.30
+unnecessary_getters_setters: 0.1.1
+unnecessary_lambdas: 0.1.30
+unnecessary_late: 1.17.0
+unnecessary_nullable_for_final_variable_declarations: 0.1.118
+unnecessary_null_aware_operator_on_extension_on_nullable: 1.24.0
+unnecessary_null_checks: 0.1.119
 unnecessary_overrides: 0.1.31
 unnecessary_parenthesis: 0.1.44
-unnecessary_raw_strings : 0.1.111
+unnecessary_raw_strings: 0.1.111
 unnecessary_statements: 0.1.36
-unnecessary_string_escapes : 0.1.111
-unnecessary_string_interpolations : 0.1.110
+unnecessary_string_escapes: 0.1.111
+unnecessary_string_interpolations: 0.1.110
 unnecessary_this: 0.1.30
+unnecessary_to_list_in_spreads: 1.24.0
+unreachable_from_main: 1.28.0
 unrelated_type_equality_checks: 0.1.16
-unsafe_html : 0.1.90
+unsafe_html: 0.1.90
+use_build_context_synchronously: 1.1.0
+use_colored_box: 1.19.0
+use_decorated_box: 1.15.0
+use_enums: 1.19.0
 use_full_hex_values_for_flutter_colors: 0.1.80
 use_function_type_syntax_for_parameters: 0.1.72
-use_is_even_rather_than_modulo : 0.1.116
-use_key_in_widget_constructors : 0.1.108
-use_late_for_private_fields_and_variables : 0.1.118
-use_raw_strings : 0.1.111
+use_if_null_to_convert_nulls_to_bools: 1.0.0
+use_is_even_rather_than_modulo: 0.1.116
+use_key_in_widget_constructors: 0.1.108
+use_late_for_private_fields_and_variables: 0.1.118
+use_named_constants: 1.0.0
 use_rethrow_when_possible: 0.1.31
+use_raw_strings: 0.1.111
 use_setters_to_change_properties: 0.1.31
 use_string_buffers: 0.1.31
+use_string_in_part_of_directives: 1.27.0
+use_super_parameters: 1.20.0
+use_test_throws_matchers: 1.5.0
 use_to_and_as_if_applicable: 0.1.31
 valid_regexps: 0.1.22
 void_checks: 0.1.49
diff --git a/tool/spelunk.dart b/tool/spelunk.dart
index d37f168..51bb247 100644
--- a/tool/spelunk.dart
+++ b/tool/spelunk.dart
@@ -11,6 +11,6 @@
 
   var options = parser.parse(args);
   for (var path in options.rest) {
-    Spelunker(path).spelunk();
+    FileSpelunker(path).spelunk();
   }
 }