Add linter repository to sdk, preserving its commit history
Added using git-subtree command git subtree add -P pkg/linter linter/main Bug: https://github.com/dart-lang/linter/issues/4411
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Lint bug report
|
||||
about: Report a bug with, or propose a modification to, an existing lint.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the issue is.
|
||||
|
||||
If you're seeing unexpected behavior in a lint rule, please include the name of the rule in the issue description.
|
||||
|
||||
**To Reproduce**
|
||||
Ideally, please include an example.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -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/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
|
||||
@@ -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.
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# Description
|
||||
|
||||
Please include a summary of the change and a reference to any corresponding issues.
|
||||
If this is a fix to an existing lint rule or a proposed new one, please include
|
||||
the name of the rule in the pull request.
|
||||
|
||||
If this is a new lint or feature and there is not an existing tracking bug, please
|
||||
consider opening one to better facilitate conversation.
|
||||
|
||||
Fixes # (issue)
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
# Dependabot configuration file.
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
labels:
|
||||
- autosubmit
|
||||
Vendored
+10
@@ -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|collection_methods_unrelated_type|curly_braces_in_flow_control_structures|depend_on_referenced_packages|empty_catches|file_names|hash_and_equals|implicit_call_tearoffs|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|use_string_in_part_of_directives|valid_regexps|void_checks)'
|
||||
|
||||
set-recommended:
|
||||
- '(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)'
|
||||
Vendored
+104
@@ -0,0 +1,104 @@
|
||||
# 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/**/collection_methods_unrelated_type.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/**/implicit_call_tearoffs.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/**/use_string_in_part_of_directives.dart
|
||||
- lib/**/valid_regexps.dart
|
||||
- lib/**/void_checks.dart
|
||||
|
||||
set-recommended:
|
||||
- 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
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
name: linter
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# “At 00:00 (UTC) on Sunday.”
|
||||
- cron: '0 0 * * 0'
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 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:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
linter-bot:
|
||||
- main
|
||||
- coverage
|
||||
- benchmark
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
|
||||
- uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f
|
||||
with:
|
||||
sdk: dev
|
||||
|
||||
- run: dart pub get
|
||||
|
||||
- name: tool/bot.sh
|
||||
env:
|
||||
LINTER_BOT: ${{ matrix.linter-bot }}
|
||||
run: ./tool/bot.sh
|
||||
- name: upload coverage
|
||||
if: matrix.linter-bot == 'coverage'
|
||||
uses: coverallsapp/github-action@95b1a2355bd0e526ad2fd62da9fd386ad4c98474
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: var/lcov.info
|
||||
+21
@@ -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@98b5412841f6c4b0b3d9c29d53c13fad16bd7de2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: .github/issue-labeler.yml
|
||||
enable-versioned-regex: 0
|
||||
include-title: 1
|
||||
+17
@@ -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@ac9175f8a1f3625fd0d4fb234536d26811351594
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# A workflow to close issues where the author hasn't responded to a request for
|
||||
# more information; see https://github.com/actions/stale.
|
||||
|
||||
name: No Response
|
||||
|
||||
# Run as a daily cron.
|
||||
on:
|
||||
schedule:
|
||||
# Every day at 8am
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
# All permissions not specified are set to 'none'.
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
no-response:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'dart-lang' }}
|
||||
steps:
|
||||
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84
|
||||
with:
|
||||
days-before-stale: -1
|
||||
days-before-close: 14
|
||||
stale-issue-label: "needs-info"
|
||||
close-issue-message: >
|
||||
Without additional information we're not able to resolve this issue.
|
||||
Feel free to add more info or respond to any questions above and we
|
||||
can reopen the case. Thanks for your contribution!
|
||||
stale-pr-label: "needs-info"
|
||||
close-pr-message: >
|
||||
Without additional information we're not able to resolve this PR.
|
||||
Feel free to add more info or respond to any questions above.
|
||||
Thanks for your contribution!
|
||||
@@ -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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031
|
||||
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@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
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@0ba4244466797eb048eb91a6cd43d5c03ca8bd05
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -0,0 +1,13 @@
|
||||
.idea
|
||||
.dart_tool
|
||||
packages
|
||||
build
|
||||
.project
|
||||
.settings
|
||||
pubspec.lock
|
||||
.packages
|
||||
linter.iml
|
||||
.atom
|
||||
/.vscode
|
||||
/.history
|
||||
/doc/lints/
|
||||
@@ -0,0 +1,50 @@
|
||||
# Names should be added to this file with this pattern:
|
||||
#
|
||||
# For individuals:
|
||||
# Name <email address>
|
||||
#
|
||||
# For organizations:
|
||||
# Organization <fnmatch pattern>
|
||||
#
|
||||
|
||||
Google Inc. <*@google.com>
|
||||
|
||||
Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
|
||||
Ahmed Ashour <asashour@yahoo.com>
|
||||
Honza Bittner <git@honzabittner.cz>
|
||||
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>
|
||||
Keisuke Kishida <kishidak@tcd.ie>
|
||||
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>
|
||||
Peter Leibiger <kuhnroyal@gmail.com>
|
||||
Modestas Valauskas <valauskasmodestas@gmail.com>
|
||||
CCXXXI <ccxxxi@qq.com>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
Contributing to the Linter
|
||||
==========================
|
||||
|
||||
[](https://github.com/dart-lang/linter/actions)
|
||||

|
||||
|
||||
|
||||
Want to contribute? Great! First, read this page (including the small print at
|
||||
the end).
|
||||
|
||||
### Before you contribute
|
||||
|
||||
_See also: [Dart's code of conduct](https://dart.dev/code-of-conduct)_
|
||||
|
||||
Before we can use your code, you must sign the
|
||||
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
|
||||
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||
copyright to your changes, even after your contribution becomes part of our
|
||||
codebase, so we need your permission to use and distribute your code. We also
|
||||
need to be sure of various other things—for instance that you'll tell us if you
|
||||
know that your code infringes on other people's patents. You don't have to sign
|
||||
the CLA until after you've submitted your code for review and a member has
|
||||
approved it, but you must do it before we can put your code into our codebase.
|
||||
|
||||
Before you start working on a larger contribution, you should get in touch with
|
||||
us first through the issue tracker with your idea so that we can help out and
|
||||
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||
frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review.
|
||||
|
||||
### File headers
|
||||
All files in the project must start with the following header.
|
||||
|
||||
// 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.
|
||||
|
||||
### Mechanics
|
||||
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/.
|
||||
* `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 main repository, not your clone, when running git fetch et al.)
|
||||
|
||||
To start working on a patch:
|
||||
|
||||
* `git fetch upstream`
|
||||
* `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`
|
||||
|
||||
To send us a pull request:
|
||||
|
||||
* `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
|
||||
|
||||
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".
|
||||
|
||||
Please note that a few kinds of changes require a `CHANGELOG` entry. Notably, any change that:
|
||||
|
||||
1. adds a new lint
|
||||
2. removes or deprecates a lint or
|
||||
3. fundamentally changes the semantics of an existing lint
|
||||
|
||||
should have a short entry in the `CHANGELOG`. Feel free to bring up any questions in your PR.
|
||||
|
||||
Once you've gotten an LGTM from a project maintainer, submit your changes to the
|
||||
`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: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!
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than the
|
||||
one above, the
|
||||
[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright 2015, the Dart project authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Linter for Dart
|
||||
|
||||
The Dart Linter package defines lint rules that identify and report on "lints" found in Dart code. Linting is performed by the Dart
|
||||
analysis server and the `dart analyze` command in the [Dart command-line tool][dart_cli].
|
||||
|
||||
[](https://github.com/dart-lang/linter/actions)
|
||||
[](https://coveralls.io/r/dart-lang/linter)
|
||||
[](https://deps.dev/project/github/dart-lang%2Flinter)
|
||||
|
||||
## Installing
|
||||
|
||||
The linter is bundled with the Dart [SDK](https://dart.dev/tools/sdk); if you have an updated Dart SDK already, you're done!
|
||||
|
||||
Alternatively, if you want to contribute to the linter or examine the source, clone the `linter` repo like this:
|
||||
|
||||
$ git clone https://github.com/dart-lang/linter.git
|
||||
|
||||
## Usage
|
||||
|
||||
The linter gives you feedback to help you catch potential errors and keep your code in line with the published
|
||||
[Dart Style Guide][style_guide]. Enforceable lint rules (or "lints") are cataloged [here][lints] and can be configured via an
|
||||
[analysis options file][options_file]. The linter is run from within the `dart analyze` [command-line tool][analyzer_cli] shipped with the
|
||||
Dart SDK. Assuming you have lints configured in an `analysis_options.yaml` file at the root of your project with these contents:
|
||||
|
||||
```yaml
|
||||
linter:
|
||||
rules:
|
||||
- annotate_overrides
|
||||
- hash_and_equals
|
||||
- prefer_is_not_empty
|
||||
```
|
||||
you could lint your package like this:
|
||||
|
||||
$ 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]. 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,
|
||||
|
||||
```dart
|
||||
// ignore: camel_case_types
|
||||
class whyOhWhy { }
|
||||
```
|
||||
|
||||
tells the Dart analyzer to ignore this instance of the `camel_case_types` warning.
|
||||
|
||||
End-of-line comments are supported as well. The following communicates the same thing:
|
||||
|
||||
```dart
|
||||
class whyOhWhy { // ignore: camel_case_types
|
||||
```
|
||||
|
||||
To ignore a rule for an entire file, use the `ignore_for_file` comment flag. For example,
|
||||
|
||||
```dart
|
||||
// ignore_for_file: camel_case_types
|
||||
|
||||
...
|
||||
|
||||
class whyOhWhy { }
|
||||
```
|
||||
|
||||
tells the Dart analyzer to ignore all occurrences of the `camel_case_types` warning in this file.
|
||||
|
||||
As lints are treated the same as errors and warnings by the analyzer, their severity can similarly be configured in an options file. For
|
||||
example, an analysis options file that specifies
|
||||
|
||||
```yaml
|
||||
linter:
|
||||
rules:
|
||||
- camel_case_types
|
||||
analyzer:
|
||||
errors:
|
||||
camel_case_types: error
|
||||
```
|
||||
|
||||
tells the analyzer to treat `camel_case_types` lints as errors. For more on configuring analysis see the analysis option file [docs][options_file].
|
||||
|
||||
## Contributing
|
||||
|
||||
Feedback is greatly appreciated and contributions are welcome! Please read the
|
||||
[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://dart.dev/tools/dart-analyze
|
||||
[dart_cli]: https://dart.dev/tools/dart-tool
|
||||
[effective_dart]: https://dart.dev/effective-dart
|
||||
[lints]: https://dart.dev/lints
|
||||
[options_file]: https://dart.dev/guides/language/analysis-options#the-analysis-options-file
|
||||
[package-dart-lints]: https://github.com/dart-lang/lints
|
||||
[package-flutter-lints]: https://github.com/flutter/packages/tree/main/packages/flutter_lints
|
||||
[style_guide]: https://dart.dev/effective-dart/style
|
||||
[tracker]: https://github.com/dart-lang/linter/issues
|
||||
@@ -0,0 +1,67 @@
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
# ignore vscode .history files
|
||||
- .history/**
|
||||
- test_data/**
|
||||
language:
|
||||
strict-casts: true
|
||||
linter:
|
||||
rules:
|
||||
- always_put_required_named_parameters_first
|
||||
- avoid_annotating_with_dynamic
|
||||
- avoid_catches_without_on_clauses
|
||||
- avoid_catching_errors
|
||||
- avoid_dynamic_calls
|
||||
- avoid_positional_boolean_parameters
|
||||
- avoid_print
|
||||
- avoid_redundant_argument_values
|
||||
- avoid_returning_null
|
||||
- avoid_returning_null_for_future
|
||||
- avoid_returning_this
|
||||
- avoid_setters_without_getters
|
||||
- avoid_slow_async_io
|
||||
- avoid_unused_constructor_parameters
|
||||
- cancel_subscriptions
|
||||
- cast_nullable_to_non_nullable
|
||||
- collection_methods_unrelated_type
|
||||
- comment_references
|
||||
- dangling_library_doc_comments
|
||||
- directives_ordering
|
||||
- discarded_futures
|
||||
- implicit_call_tearoffs
|
||||
- join_return_with_assignment
|
||||
- library_annotations
|
||||
- literal_only_boolean_expressions
|
||||
- no_adjacent_strings_in_list
|
||||
- noop_primitive_operations
|
||||
- only_throw_errors
|
||||
- package_api_docs
|
||||
- parameter_assignments
|
||||
- prefer_asserts_in_initializer_lists
|
||||
- prefer_const_constructors_in_immutables
|
||||
- prefer_constructors_over_static_methods
|
||||
- prefer_expression_function_bodies
|
||||
- prefer_foreach
|
||||
- prefer_null_aware_method_calls
|
||||
- prefer_relative_imports
|
||||
- prefer_single_quotes
|
||||
- sort_pub_dependencies
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
- unawaited_futures # pedantic
|
||||
- unnecessary_breaks
|
||||
- unnecessary_final
|
||||
- unnecessary_lambdas
|
||||
- unnecessary_library_directive
|
||||
- unnecessary_null_checks
|
||||
- unnecessary_parenthesis
|
||||
- unnecessary_statements
|
||||
- unreachable_from_main
|
||||
- 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
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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:linter/src/cli.dart' as cli;
|
||||
|
||||
Future main(List<String> args) async {
|
||||
await cli.run(args);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
# The Life of a Lint
|
||||
|
||||
## Lint States
|
||||
|
||||
### Proposed
|
||||
|
||||
A lint begins its life as a **proposal** which is _pending_ until _accepted_.
|
||||
|
||||
### Accepted
|
||||
|
||||
After discussion and sufficient agreement within the team, a proposed lint becomes **accepted**.
|
||||
|
||||
An accepted lint is ready for implementation.
|
||||
|
||||
### Experimental
|
||||
|
||||
Lints can be marked experimental.
|
||||
|
||||
Reasons a lint might be marked experimental include a lint
|
||||
|
||||
* being tentatively introduced
|
||||
* having unknown value
|
||||
* having incomplete semantics
|
||||
* having outstanding (fixable) false positives or
|
||||
* that might be known to be temporary.
|
||||
|
||||
### Stable
|
||||
|
||||
Stable lints are lints whose semantics and implementation are considered complete. False
|
||||
positives (that are not known, accepted and documented limitations of lint semantics) are
|
||||
considered bugs and fixing them should be prioritized. False negatives may be bugs or
|
||||
enhancements (depending on semantics).
|
||||
|
||||
### Deprecated
|
||||
|
||||
Deprecated lints are lints that we plan to remove.
|
||||
|
||||
Reasons for deprecation include:
|
||||
|
||||
* semantics that don't make sense with current language semantics (e.g., null-safety)
|
||||
* stale advice
|
||||
* poor performance or
|
||||
* poor user experience (e.g., too many false positives).
|
||||
|
||||
### Removed
|
||||
|
||||
Lints we no longer support are removed.
|
||||
|
||||
In general removal is preceded by a period of deprecation.
|
||||
|
||||
## State Transitions
|
||||
|
||||
### Implementation
|
||||
|
||||
Once a lint proposal is accepted, it is safe to implement. A change that lands a new lint should
|
||||
have a corresponding `CHANGELOG` entry.
|
||||
|
||||
### Deprecation
|
||||
|
||||
Implemented lints can be deprecated.
|
||||
|
||||
Deprecating lints that are in common lint sets (e.g., in [package:lints](https://github.com/dart-lang/lints)
|
||||
can be impactful so should be done with care.
|
||||
|
||||
(**NOTE**: A change that deprecates an existing lint should have a corresponding `CHANGELOG` entry.)
|
||||
|
||||
### Marking Stable
|
||||
|
||||
Experimental lints should aspire to be stable. An experimental lint is a candidate for stable when it has
|
||||
|
||||
* complete semantics
|
||||
* complete implementation (with no false-positives)
|
||||
* established long-term value (e.g., inclusion in a recommended lint set)
|
||||
|
||||
### Removal
|
||||
|
||||
Deprecated or experimental lints can be removed. A stable lint should be deprecated before removal.
|
||||
|
||||
(**NOTE**: A change that removes an existing lint should have a corresponding `CHANGELOG` entry.)
|
||||
@@ -0,0 +1,119 @@
|
||||
# `use_build_context_synchronously` design
|
||||
|
||||
## Background
|
||||
|
||||
The idea behind the `use_build_context_synchronously` lint rule requires
|
||||
careful tracking of a function body's possible control flow. At various points
|
||||
in the syntax tree, we must be able to answer questions like, "when this
|
||||
expression is reached, is it _possible_ that we have traversed through an
|
||||
asynchronous gap?" and "does this mounted check _definitely_ guard this
|
||||
expression?" The intricacies of such tracking are quite different from the
|
||||
requirements of most linter rules.
|
||||
|
||||
## High level design
|
||||
|
||||
At a high level, the task of the lint rule can be broken down into three steps:
|
||||
|
||||
1. Consider each expression which references a BuildContext value, and which
|
||||
may be vulnerable to async gap bugs. For each such expression:
|
||||
2. Walk up the syntax tree from the expression, until reaching the boundary of a
|
||||
function body (don't walk outside an anonymous function body, or a method
|
||||
declaration, etc.). At each ancestor node (well, not exactly ancestor, see
|
||||
below):
|
||||
3. Compute the "async state" between the ancestor node and the expression-in-
|
||||
question, by visiting the ancestor's descendent nodes, looking for await
|
||||
expressions and mounted checks.
|
||||
|
||||
The first step just uses the linter's standard rule-registering mechanism. The
|
||||
latter steps are explained below.
|
||||
|
||||
### Mounted guards
|
||||
|
||||
It would be egregious to require a function with a reference to a BuildContext
|
||||
to have _zero_ async gaps (await expressions) above the reference. Developers
|
||||
are allowed a safeguard: a "mounted check" which leads to a "mounted guard."
|
||||
|
||||
* A "mounted check" is merely an expression in which the `mounted` property of a
|
||||
BuildContext expression is read. It typically looks like `context.mounted`.
|
||||
* A mounted check leads to a "mounted guard" for a certain node if it guarantees
|
||||
that the node will only be executed if the BuildContext is mounted, that is,
|
||||
the mounted check has returned positive.
|
||||
|
||||
## Walking up the ancestors and their preceding siblings
|
||||
|
||||
In this step, we have a single expression with a **reference** to a BuildContext
|
||||
object, and we need to examine nodes which contain an async gap _which could be
|
||||
crossed before the access to the **reference**_. This means we are simulating,
|
||||
to a very limited extent, the runtime flow between an async gap and
|
||||
**reference**.
|
||||
|
||||
1. Start with the expression containing the **reference**.
|
||||
2. Define a **child** node whose value is initially **reference**.
|
||||
3. While **child** is not a function body:
|
||||
a. Set a **parent** node equal to the child's parent.
|
||||
b. Check the "async state" between **parent** and **child**.
|
||||
c. If the state is "asynchronous," then there is a _possible_ async gap
|
||||
between **parent** and **child**, so report a lint.
|
||||
d. If the state is "mounted guard," then there is a _definite_ mounted
|
||||
guard between **parent** and **child**, so **child** is safe, and we can
|
||||
stop checking **child**.
|
||||
|
||||
Note: We use **child** in the loop, rather than just **reference**, in order to
|
||||
make use of **child**'s relationship to **parent**, when computing the "async
|
||||
gap" (see the next section).
|
||||
|
||||
Given the way we walk up the syntax tree, the async state between **parent** and
|
||||
**child** is the same state as between **parent** and **reference**: if there is
|
||||
a _possible_ async gap between **parent** and **child**, then there is a
|
||||
possible async gap between **parent** and **reference**, and if there is a
|
||||
_definite_ mounted guard between **parent** and **child**, then there is a
|
||||
_definite_ mounted guard between **parent** and **reference**.
|
||||
|
||||
## Computing the "async state" of one node relative to another
|
||||
|
||||
This is the most complex and delicate step. Given two nodes, a **parent** and
|
||||
**child**, we must calculate whether there is a _possible_ async gap between the
|
||||
two (with no mounted guard between the async gap and the **child**), or a
|
||||
_definite_ mounted guard between the two (without a possible async gap between
|
||||
the mounted guard and **child**), or no interesting async state between the two.
|
||||
This calculation is based on a few simple properties:
|
||||
|
||||
* We implement this calculation with a standard SimpleVisitor from the analyzer.
|
||||
We create the visitor with **child** as the reference node to consider. Each
|
||||
visit method descends down various child nodes, receiving their returned
|
||||
"async state" in order to calculate the state of it's own visited node,
|
||||
relative to **child**.
|
||||
* At the entrypoint of the visitation, **child**'s parent is **parent**. Then we
|
||||
descend into **parent**'s various descendents, and so for every other visit
|
||||
method, **child** is a sibling of the visited node (in a NodeList), or is a
|
||||
child of some ancestor of the visited node.
|
||||
* For nodes with multiple children, the associated visit method must take the
|
||||
position of **child** into account, as an async gap only affects **child** if
|
||||
it occurs _before_ **child**. Examples:
|
||||
* YieldStatement - this node has one child. If **child** is that child, then
|
||||
the async state is "uninteresting" (`null`). Otherwise, **child** follows
|
||||
the YieldStatement, and any await expressions occurring in the
|
||||
YieldStatement's expression result in `AsyncState.asynchronous`.
|
||||
* Block - this node has a list of child Statements. If **child** is one of
|
||||
those, then we compute the async state between each Statement that precedes
|
||||
**child** and **child**. We do not consider the Statements that follow
|
||||
**child** (unless the parent of the Block is a DoStatement, ForStatement, or
|
||||
WhileStatement). We consider each preceding Statement in reverse order,
|
||||
starting with the Statement that immediately precedes **child**. In this way
|
||||
we can correctly identify that a Statement which acts as a mounted guard for
|
||||
**child**. If **child** is not one of the Statements, then it follows the
|
||||
Block, and any await expressions occurring in the Block's expressions result
|
||||
in `AsyncState.asynchronous`.
|
||||
* MethodInvocation - this node has a target Expression and an argument list of
|
||||
Expressions. At runtime, the target is evaluated before the argument list,
|
||||
and the arguments are evaluated in left-to-right order. The associated visit
|
||||
method uses these facts to compute, for example, that if **child** is the
|
||||
target, then any async gaps in the argument list do not affect it.
|
||||
* For nodes which can affect control flow (IfStatement, IfElement,
|
||||
ConditionalExpression, etc.), the associated visit methods must take
|
||||
**child**'s position into account for the purposes of mounted guards. For
|
||||
example, an IfStatement with a condition like `context.mounted` guards the
|
||||
then-statement, but not the else-statement, and no statements that follow the
|
||||
IfStatement. An IfStatement with a condition like `!context.mounted` and a
|
||||
then-statement that definitely exits (e.g. with a return or a throw),
|
||||
definitely guards the statements that follow it.
|
||||
@@ -0,0 +1,169 @@
|
||||
# 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 unit tests live in
|
||||
[test/rules].
|
||||
|
||||
Rule stubs can be generated with the [rule.dart] helper script:
|
||||
|
||||
$ dart tool/rule.dart -n my_new_lint
|
||||
|
||||
generates lint and test stubs in `lib/src/rules` and `test/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]. *Wherever 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/effective-dart/style/
|
||||
[lints]: https://dart.dev/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
|
||||
[analyzer.dart utility library]: https://github.com/dart-lang/linter/blob/main/lib/src/analyzer.dart
|
||||
[implementation_imports]: https://dart.dev/lints/implementation_imports
|
||||
[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
|
||||
@@ -0,0 +1,14 @@
|
||||
# Examples
|
||||
|
||||
Some example analysis options files.
|
||||
|
||||
* [all.yaml](all.yaml) - auto-generated options enabling all lints.
|
||||
|
||||
For more on lint rule sets see the analyzer docs on
|
||||
[customizing static analysis](https://dart.dev/guides/language/analysis-options).
|
||||
|
||||
Official Dart lint rules are maintained in
|
||||
[`package:lints`](https://github.com/dart-lang/lints/).
|
||||
|
||||
Flutter team rules are maintained in
|
||||
[`package:flutter_lints`](https://github.com/flutter/packages/tree/main/packages/flutter_lints).
|
||||
@@ -0,0 +1,220 @@
|
||||
# Auto-generated options enabling all lints.
|
||||
# Add these to your `analysis_options.yaml` file and tailor to fit!
|
||||
linter:
|
||||
rules:
|
||||
- always_declare_return_types
|
||||
- always_put_control_body_on_new_line
|
||||
- always_put_required_named_parameters_first
|
||||
- always_specify_types
|
||||
- always_use_package_imports
|
||||
- annotate_overrides
|
||||
- avoid_annotating_with_dynamic
|
||||
- avoid_bool_literals_in_conditional_expressions
|
||||
- avoid_catches_without_on_clauses
|
||||
- avoid_catching_errors
|
||||
- avoid_classes_with_only_static_members
|
||||
- avoid_double_and_int_checks
|
||||
- avoid_dynamic_calls
|
||||
- avoid_empty_else
|
||||
- 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
|
||||
- avoid_js_rounded_ints
|
||||
- avoid_multiple_declarations_per_line
|
||||
- avoid_null_checks_in_equality_operators
|
||||
- avoid_positional_boolean_parameters
|
||||
- avoid_print
|
||||
- avoid_private_typedef_functions
|
||||
- avoid_redundant_argument_values
|
||||
- avoid_relative_lib_imports
|
||||
- avoid_renaming_method_parameters
|
||||
- avoid_return_types_on_setters
|
||||
- avoid_returning_null_for_void
|
||||
- avoid_returning_this
|
||||
- avoid_setters_without_getters
|
||||
- avoid_shadowing_type_parameters
|
||||
- avoid_single_cascade_in_expression_statements
|
||||
- avoid_slow_async_io
|
||||
- avoid_type_to_string
|
||||
- avoid_types_as_parameter_names
|
||||
- avoid_types_on_closure_parameters
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_unused_constructor_parameters
|
||||
- avoid_void_async
|
||||
- avoid_web_libraries_in_flutter
|
||||
- await_only_futures
|
||||
- camel_case_extensions
|
||||
- camel_case_types
|
||||
- cancel_subscriptions
|
||||
- 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
|
||||
- deprecated_member_use_from_same_package
|
||||
- diagnostic_describe_all_properties
|
||||
- directives_ordering
|
||||
- discarded_futures
|
||||
- do_not_use_environment
|
||||
- empty_catches
|
||||
- empty_constructor_bodies
|
||||
- empty_statements
|
||||
- eol_at_end_of_file
|
||||
- exhaustive_cases
|
||||
- file_names
|
||||
- flutter_style_todos
|
||||
- hash_and_equals
|
||||
- implementation_imports
|
||||
- implicit_call_tearoffs
|
||||
- implicit_reopen
|
||||
- invalid_case_patterns
|
||||
- join_return_with_assignment
|
||||
- leading_newlines_in_multiline_strings
|
||||
- library_annotations
|
||||
- library_names
|
||||
- library_prefixes
|
||||
- library_private_types_in_public_api
|
||||
- lines_longer_than_80_chars
|
||||
- literal_only_boolean_expressions
|
||||
- matching_super_parameters
|
||||
- missing_whitespace_between_adjacent_strings
|
||||
- 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_literal_bool_comparisons
|
||||
- no_logic_in_create_state
|
||||
- no_runtimeType_toString
|
||||
- no_self_assignments
|
||||
- no_wildcard_variable_uses
|
||||
- non_constant_identifier_names
|
||||
- noop_primitive_operations
|
||||
- null_check_on_nullable_type_parameter
|
||||
- null_closures
|
||||
- omit_local_variable_types
|
||||
- one_member_abstracts
|
||||
- only_throw_errors
|
||||
- overridden_fields
|
||||
- package_api_docs
|
||||
- package_names
|
||||
- package_prefixed_library_names
|
||||
- parameter_assignments
|
||||
- prefer_adjacent_string_concatenation
|
||||
- prefer_asserts_in_initializer_lists
|
||||
- prefer_asserts_with_message
|
||||
- prefer_collection_literals
|
||||
- prefer_conditional_assignment
|
||||
- prefer_const_constructors
|
||||
- prefer_const_constructors_in_immutables
|
||||
- prefer_const_declarations
|
||||
- prefer_const_literals_to_create_immutables
|
||||
- prefer_constructors_over_static_methods
|
||||
- prefer_contains
|
||||
- prefer_double_quotes
|
||||
- prefer_expression_function_bodies
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
- prefer_final_parameters
|
||||
- prefer_for_elements_to_map_fromIterable
|
||||
- prefer_foreach
|
||||
- prefer_function_declarations_over_variables
|
||||
- prefer_generic_function_type_aliases
|
||||
- prefer_if_elements_to_conditional_expressions
|
||||
- prefer_if_null_operators
|
||||
- prefer_initializing_formals
|
||||
- prefer_inlined_adds
|
||||
- prefer_int_literals
|
||||
- prefer_interpolation_to_compose_strings
|
||||
- prefer_is_empty
|
||||
- prefer_is_not_empty
|
||||
- prefer_is_not_operator
|
||||
- prefer_iterable_whereType
|
||||
- prefer_mixin
|
||||
- prefer_null_aware_method_calls
|
||||
- prefer_null_aware_operators
|
||||
- prefer_relative_imports
|
||||
- prefer_single_quotes
|
||||
- prefer_spread_collections
|
||||
- prefer_typing_uninitialized_variables
|
||||
- prefer_void_to_null
|
||||
- provide_deprecation_message
|
||||
- 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
|
||||
- sort_pub_dependencies
|
||||
- sort_unnamed_constructors_first
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
- tighten_type_of_initializing_formals
|
||||
- type_annotate_public_apis
|
||||
- type_init_formals
|
||||
- type_literal_in_constant_pattern
|
||||
- unawaited_futures
|
||||
- unnecessary_await_in_return
|
||||
- unnecessary_brace_in_string_interps
|
||||
- unnecessary_breaks
|
||||
- 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
|
||||
- unnecessary_overrides
|
||||
- unnecessary_parenthesis
|
||||
- unnecessary_raw_strings
|
||||
- unnecessary_statements
|
||||
- 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
|
||||
- use_is_even_rather_than_modulo
|
||||
- use_key_in_widget_constructors
|
||||
- use_late_for_private_fields_and_variables
|
||||
- use_named_constants
|
||||
- use_raw_strings
|
||||
- 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
|
||||
- void_checks
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2017, 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.
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart' show AnalysisErrorInfo;
|
||||
import 'package:analyzer/src/lint/io.dart' show errorSink;
|
||||
import 'package:analyzer/src/lint/linter.dart';
|
||||
import 'package:analyzer/src/lint/registry.dart';
|
||||
import 'package:analyzer/src/lint/util.dart' as util;
|
||||
import 'package:analyzer/src/services/lint.dart' as lint_service;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
|
||||
export 'package:analyzer/dart/element/type_system.dart';
|
||||
export 'package:analyzer/src/dart/ast/token.dart';
|
||||
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 AnalysisErrorInfo;
|
||||
export 'package:analyzer/src/generated/source.dart' show LineInfo, Source;
|
||||
export 'package:analyzer/src/lint/linter.dart'
|
||||
show
|
||||
dart3,
|
||||
DartLinter,
|
||||
Group,
|
||||
LintFilter,
|
||||
LintRule,
|
||||
LinterContext,
|
||||
LinterOptions,
|
||||
Maturity,
|
||||
NodeLintRegistry,
|
||||
NodeLintRule,
|
||||
State;
|
||||
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;
|
||||
|
||||
const loggedAnalyzerErrorExitCode = 63;
|
||||
|
||||
/// A version describing Dart language version 2.12.0.
|
||||
// todo(pq): remove when analyzer 5.5.0 is published.
|
||||
final Version dart2_12 = Version(2, 12, 0);
|
||||
|
||||
Future<Iterable<AnalysisErrorInfo>> lintFiles(
|
||||
DartLinter linter, List<File> filesToLint) async {
|
||||
// Setup an error watcher to track whether an error was logged to stderr so
|
||||
// we can set the exit code accordingly.
|
||||
var errorWatcher = ErrorWatchingSink(errorSink);
|
||||
errorSink = errorWatcher;
|
||||
|
||||
var errors = await linter.lintFiles(filesToLint);
|
||||
if (errorWatcher.encounteredError) {
|
||||
exitCode = loggedAnalyzerErrorExitCode;
|
||||
} else if (errors.isNotEmpty) {
|
||||
exitCode = _maxSeverity(errors.toList(), linter.options.filter);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
Iterable<AnalysisError> _filtered(
|
||||
List<AnalysisError> errors, LintFilter? filter) =>
|
||||
(filter == null)
|
||||
? errors
|
||||
: errors.where((AnalysisError e) => !filter.filter(e));
|
||||
|
||||
int _maxSeverity(List<AnalysisErrorInfo> errors, LintFilter? filter) {
|
||||
var max = 0;
|
||||
for (var info in errors) {
|
||||
_filtered(info.errors, filter).forEach((AnalysisError e) {
|
||||
max = math.max(max, e.errorCode.errorSeverity.ordinal);
|
||||
});
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/// Facade for managing access to `analyzer` package APIs.
|
||||
class Analyzer {
|
||||
/// Shared instance.
|
||||
static Analyzer facade = Analyzer();
|
||||
|
||||
/// Returns currently registered lint rules.
|
||||
Iterable<LintRule> get registeredRules => Registry.ruleRegistry;
|
||||
|
||||
/// Cache linter version; used in summary signatures.
|
||||
void cacheLinterVersion() {
|
||||
// todo(pq): remove (https://github.com/dart-lang/linter/issues/4418)
|
||||
lint_service.linterVersion = '1.35.0';
|
||||
}
|
||||
|
||||
/// Create a library name prefix based on [libraryPath], [projectRoot] and
|
||||
/// current [packageName].
|
||||
String createLibraryNamePrefix(
|
||||
{required String libraryPath,
|
||||
String? projectRoot,
|
||||
String? packageName}) =>
|
||||
util.createLibraryNamePrefix(
|
||||
libraryPath: libraryPath,
|
||||
projectRoot: projectRoot,
|
||||
packageName: packageName);
|
||||
|
||||
/// Register this [lint] with the analyzer's rule registry.
|
||||
void register(LintRule lint) {
|
||||
Registry.ruleRegistry.register(lint);
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorWatchingSink implements IOSink {
|
||||
bool encounteredError = false;
|
||||
|
||||
IOSink delegate;
|
||||
|
||||
ErrorWatchingSink(this.delegate);
|
||||
|
||||
@override
|
||||
Future get done => delegate.done;
|
||||
|
||||
@override
|
||||
Encoding get encoding => delegate.encoding;
|
||||
|
||||
@override
|
||||
set encoding(Encoding encoding) {
|
||||
delegate.encoding = encoding;
|
||||
}
|
||||
|
||||
@override
|
||||
void add(List<int> data) {
|
||||
delegate.add(data);
|
||||
}
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace? stackTrace]) {
|
||||
encounteredError = true;
|
||||
delegate.addError(error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
Future addStream(Stream<List<int>> stream) => delegate.addStream(stream);
|
||||
|
||||
@override
|
||||
Future close() => delegate.close();
|
||||
|
||||
@override
|
||||
Future flush() => delegate.flush();
|
||||
|
||||
@override
|
||||
void write(Object? obj) {
|
||||
delegate.write(obj);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeAll(Iterable objects, [String separator = '']) {
|
||||
delegate.writeAll(objects, separator);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeCharCode(int charCode) {
|
||||
delegate.writeCharCode(charCode);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeln([Object? obj = '']) {
|
||||
// 'Exception while using a Visitor to visit ...' (
|
||||
if (obj.toString().startsWith('Exception')) {
|
||||
encounteredError = true;
|
||||
}
|
||||
delegate.writeln(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
// 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.
|
||||
|
||||
/// Common AST helpers.
|
||||
library;
|
||||
|
||||
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';
|
||||
import 'package:analyzer/file_system/file_system.dart';
|
||||
import 'package:analyzer/src/workspace/workspace.dart'; // ignore: implementation_imports
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
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>[];
|
||||
visitChildren(parent, (Element element) {
|
||||
if (name == null || element.displayName == name) {
|
||||
children.add(element);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
/// Return the compilation unit of a node
|
||||
CompilationUnit? getCompilationUnit(AstNode node) =>
|
||||
node.thisOrAncestorOfType<CompilationUnit>();
|
||||
|
||||
/// Returns a field identifier with the given [name] in the given [decl]'s
|
||||
/// variable declaration list or `null` if none is found.
|
||||
Token? getFieldName(FieldDeclaration decl, String name) {
|
||||
for (var v in decl.fields.variables) {
|
||||
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.
|
||||
SyntacticEntity getNodeToAnnotate(Declaration node) {
|
||||
// TODO(srawlins): Convert to a switch expression over `Declaration` subtypes,
|
||||
// assuming `Declaration` becomes an exhaustive type.
|
||||
if (node is ClassDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is ClassTypeAlias) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is ConstructorDeclaration) {
|
||||
return node.name ?? node;
|
||||
}
|
||||
if (node is EnumConstantDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is EnumDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is ExtensionDeclaration) {
|
||||
return node.name ?? node;
|
||||
}
|
||||
if (node is FieldDeclaration) {
|
||||
return node.fields;
|
||||
}
|
||||
if (node is FunctionDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is FunctionTypeAlias) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is GenericTypeAlias) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is MethodDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is MixinDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is TopLevelVariableDeclaration) {
|
||||
return node.variables;
|
||||
}
|
||||
if (node is TypeParameter) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is VariableDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
if (node is ExtensionTypeDeclaration) {
|
||||
return node.name;
|
||||
}
|
||||
assert(false, "Unaccounted for Declaration subtype: '${node.runtimeType}'");
|
||||
return node;
|
||||
}
|
||||
|
||||
/// If the [node] is the finishing identifier of an assignment, return its
|
||||
/// "writeElement", otherwise return its "staticElement", which might be
|
||||
/// thought as the "readElement".
|
||||
Element? getWriteOrReadElement(SimpleIdentifier node) {
|
||||
var writeElement = _getWriteElement(node);
|
||||
if (writeElement != null) {
|
||||
return writeElement;
|
||||
}
|
||||
return node.staticElement;
|
||||
}
|
||||
|
||||
bool hasConstantError(LinterContext context, Expression node) {
|
||||
var result = context.evaluateConstant(node);
|
||||
return result.errors.isNotEmpty;
|
||||
}
|
||||
|
||||
/// Returns `true` if this [element] has a `@literal` annotation.
|
||||
@Deprecated('prefer: element.hasLiteral')
|
||||
bool hasLiteralAnnotation(Element element) => element.hasLiteral;
|
||||
|
||||
/// Returns `true` if this [element] has an `@override` annotation.
|
||||
@Deprecated('prefer: element.hasOverride')
|
||||
bool hasOverrideAnnotation(Element element) => element.hasOverride;
|
||||
|
||||
/// Returns `true` if this [node] is the child of a private compilation unit
|
||||
/// member.
|
||||
bool inPrivateMember(AstNode node) {
|
||||
var parent = node.parent;
|
||||
if (parent is NamedCompilationUnitMember) {
|
||||
return isPrivate(parent.name);
|
||||
}
|
||||
if (parent is ExtensionDeclaration) {
|
||||
return parent.name == null || isPrivate(parent.name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns `true` if this element is the `==` method declaration.
|
||||
bool isEquals(ClassMember element) =>
|
||||
element is MethodDeclaration && element.name.lexeme == '==';
|
||||
|
||||
/// Returns `true` if the keyword associated with this token is `final` or
|
||||
/// `const`.
|
||||
bool isFinalOrConst(Token token) =>
|
||||
isKeyword(token, Keyword.FINAL) || isKeyword(token, Keyword.CONST);
|
||||
|
||||
/// Returns `true` if this element is a `hashCode` method or field declaration.
|
||||
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.
|
||||
bool isInLibDir(CompilationUnit node, WorkspacePackage? package) {
|
||||
if (package == null) return false;
|
||||
var cuPath = node.declaredElement?.library.source.fullName;
|
||||
if (cuPath == null) return false;
|
||||
var libDir = path.join(package.root, 'lib');
|
||||
return path.isWithin(libDir, cuPath);
|
||||
}
|
||||
|
||||
/// Return `true` if this compilation unit [node] is declared within a public
|
||||
/// directory in the given [package]'s directory tree. Public dirs are the
|
||||
/// `lib` and `bin` dirs.
|
||||
//
|
||||
// TODO: move into WorkspacePackage
|
||||
bool isInPublicDir(CompilationUnit node, WorkspacePackage? package) {
|
||||
if (package == null) return false;
|
||||
var cuPath = node.declaredElement?.library.source.fullName;
|
||||
if (cuPath == null) return false;
|
||||
var libDir = path.join(package.root, 'lib');
|
||||
var binDir = path.join(package.root, 'bin');
|
||||
return path.isWithin(libDir, cuPath) || path.isWithin(binDir, cuPath);
|
||||
}
|
||||
|
||||
/// Returns `true` if the keyword associated with the given [token] matches
|
||||
/// [keyword].
|
||||
bool isKeyword(Token token, Keyword keyword) =>
|
||||
token is KeywordToken && token.keyword == keyword;
|
||||
|
||||
/// Returns `true` if the given [id] is a Dart keyword.
|
||||
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(Token? name) =>
|
||||
name != null ? Identifier.isPrivateName(name.lexeme) : false;
|
||||
|
||||
/// Returns `true` if the given [ClassMember] is a public method.
|
||||
bool isPublicMethod(ClassMember m) {
|
||||
var declaredElement = m.declaredElement;
|
||||
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:
|
||||
///
|
||||
/// ```dart
|
||||
/// get x => _simpleIdentifier;
|
||||
/// ```
|
||||
///
|
||||
/// or
|
||||
///
|
||||
/// ```dart
|
||||
/// get x {
|
||||
/// return _simpleIdentifier;
|
||||
/// }
|
||||
/// ```
|
||||
bool isSimpleGetter(MethodDeclaration declaration) {
|
||||
if (!declaration.isGetter) {
|
||||
return false;
|
||||
}
|
||||
var body = declaration.body;
|
||||
if (body is ExpressionFunctionBody) {
|
||||
return _checkForSimpleGetter(declaration, body.expression);
|
||||
} else if (body is BlockFunctionBody) {
|
||||
var block = body.block;
|
||||
if (block.statements.length == 1) {
|
||||
var statement = block.statements.first;
|
||||
if (statement is ReturnStatement) {
|
||||
return _checkForSimpleGetter(declaration, statement.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns `true` if the given [setter] is a "simple setter".
|
||||
///
|
||||
/// A simple setter takes this basic form:
|
||||
///
|
||||
/// ```dart
|
||||
/// int _x;
|
||||
/// set(int x) {
|
||||
/// _x = x;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// or:
|
||||
///
|
||||
/// ```dart
|
||||
/// int _x;
|
||||
/// set(int x) => _x = x;
|
||||
/// ```
|
||||
///
|
||||
/// where the static type of the left and right hand sides of the assignment
|
||||
/// expression are the same.
|
||||
bool isSimpleSetter(MethodDeclaration setter) {
|
||||
var body = setter.body;
|
||||
if (body is ExpressionFunctionBody) {
|
||||
return _checkForSimpleSetter(setter, body.expression);
|
||||
} else if (body is BlockFunctionBody) {
|
||||
var block = body.block;
|
||||
if (block.statements.length == 1) {
|
||||
var statement = block.statements.first;
|
||||
if (statement is ExpressionStatement) {
|
||||
return _checkForSimpleSetter(setter, statement.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Return the nearest enclosing pubspec file.
|
||||
File? locatePubspecFile(CompilationUnit compilationUnit) {
|
||||
var fullName = compilationUnit.declaredElement?.source.fullName;
|
||||
if (fullName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var resourceProvider =
|
||||
compilationUnit.declaredElement?.session.resourceProvider;
|
||||
if (resourceProvider == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var file = resourceProvider.getFile(fullName);
|
||||
|
||||
// Look for a pubspec.yaml file.
|
||||
for (var folder in file.parent.withAncestors) {
|
||||
var pubspecFile = folder.getChildAssumingFile('pubspec.yaml');
|
||||
if (pubspecFile.exists) {
|
||||
return pubspecFile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Uses [processor] to visit all of the children of [element].
|
||||
/// If [processor] returns `true`, then children of a child are visited too.
|
||||
void visitChildren(Element element, ElementProcessor processor) {
|
||||
element.visitChildren(_ElementVisitorAdapter(processor));
|
||||
}
|
||||
|
||||
bool _checkForSimpleGetter(MethodDeclaration getter, Expression? expression) {
|
||||
if (expression is SimpleIdentifier) {
|
||||
var staticElement = expression.staticElement;
|
||||
if (staticElement is PropertyAccessorElement) {
|
||||
var enclosingElement = getter.declaredElement?.enclosingElement;
|
||||
// Skipping library level getters, test that the enclosing element is
|
||||
// the same
|
||||
if (staticElement.enclosingElement == enclosingElement) {
|
||||
return staticElement.isSynthetic && staticElement.variable.isPrivate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _checkForSimpleSetter(MethodDeclaration setter, Expression expression) {
|
||||
if (expression is! AssignmentExpression) {
|
||||
return false;
|
||||
}
|
||||
if (expression.operator.type != TokenType.EQ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var leftHandSide = expression.leftHandSide;
|
||||
var rightHandSide = expression.rightHandSide;
|
||||
if (leftHandSide is SimpleIdentifier && rightHandSide is SimpleIdentifier) {
|
||||
var leftElement = expression.writeElement;
|
||||
if (leftElement is! PropertyAccessorElement || !leftElement.isSynthetic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// To guard against setters used as type constraints
|
||||
if (expression.writeType != rightHandSide.staticType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var rightElement = rightHandSide.staticElement;
|
||||
if (rightElement is! ParameterElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameters = setter.parameters?.parameters;
|
||||
if (parameters != null && parameters.length == 1) {
|
||||
return rightElement == parameters.first.declaredElement;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// If the [node] is the target of a [CompoundAssignmentExpression],
|
||||
/// return the corresponding "writeElement", which is the local variable,
|
||||
/// the setter referenced with a [SimpleIdentifier] or a [PropertyAccess],
|
||||
/// or the `[]=` operator.
|
||||
Element? _getWriteElement(AstNode node) {
|
||||
var parent = node.parent;
|
||||
if (parent is AssignmentExpression && parent.leftHandSide == node) {
|
||||
return parent.writeElement;
|
||||
}
|
||||
if (parent is PostfixExpression) {
|
||||
return parent.writeElement;
|
||||
}
|
||||
if (parent is PrefixExpression) {
|
||||
return parent.writeElement;
|
||||
}
|
||||
|
||||
if (parent is PrefixedIdentifier && parent.identifier == node) {
|
||||
return _getWriteElement(parent);
|
||||
}
|
||||
|
||||
if (parent is PropertyAccess && parent.propertyName == node) {
|
||||
return _getWriteElement(parent);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/// A [GeneralizingElementVisitor] adapter for [ElementProcessor].
|
||||
class _ElementVisitorAdapter extends GeneralizingElementVisitor {
|
||||
final ElementProcessor processor;
|
||||
|
||||
_ElementVisitorAdapter(this.processor);
|
||||
|
||||
@override
|
||||
void visitElement(Element element) {
|
||||
var visitChildren = processor(element);
|
||||
if (visitChildren) {
|
||||
element.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AstNodeExtension on AstNode {
|
||||
bool get isToStringInvocationWithArguments {
|
||||
var self = this;
|
||||
return self is MethodInvocation &&
|
||||
self.methodName.name == 'toString' &&
|
||||
self.argumentList.arguments.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/lint/config.dart'; // ignore: implementation_imports
|
||||
import 'package:analyzer/src/lint/io.dart'; // ignore: implementation_imports
|
||||
import 'package:analyzer/src/lint/registry.dart'; // ignore: implementation_imports
|
||||
import 'package:args/args.dart';
|
||||
|
||||
import 'analyzer.dart';
|
||||
import 'extensions.dart';
|
||||
import 'formatter.dart';
|
||||
import 'rules.dart';
|
||||
|
||||
const processFileFailedExitCode = 65;
|
||||
const unableToProcessExitCode = 64;
|
||||
|
||||
String? getRoot(List<String> paths) =>
|
||||
paths.length == 1 && Directory(paths.first).existsSync()
|
||||
? paths.first
|
||||
: null;
|
||||
|
||||
bool isLinterErrorCode(int code) =>
|
||||
code == unableToProcessExitCode || code == processFileFailedExitCode;
|
||||
|
||||
void printUsage(ArgParser parser, IOSink out, [String? error]) {
|
||||
var message = 'Lints Dart source files and pubspecs.';
|
||||
if (error != null) {
|
||||
message = error;
|
||||
}
|
||||
|
||||
out.writeln('''$message
|
||||
Usage: linter <file>
|
||||
${parser.usage}
|
||||
|
||||
For more information, see https://github.com/dart-lang/linter
|
||||
''');
|
||||
}
|
||||
|
||||
/// Start linting from the command-line.
|
||||
Future run(List<String> args) async {
|
||||
await runLinter(args, LinterOptions());
|
||||
}
|
||||
|
||||
/// todo (pq): consider using `dart analyze` where possible
|
||||
/// see: https://github.com/dart-lang/linter/pull/2537
|
||||
Future runLinter(List<String> args, LinterOptions initialLintOptions) async {
|
||||
// Force the rule registry to be populated.
|
||||
registerLintRules();
|
||||
|
||||
var parser = ArgParser();
|
||||
parser
|
||||
..addFlag('help',
|
||||
abbr: 'h', negatable: false, help: 'Show usage information.')
|
||||
..addFlag('stats',
|
||||
abbr: 's', negatable: false, help: 'Show lint statistics.')
|
||||
..addFlag('benchmark', negatable: false, help: 'Show lint benchmarks.')
|
||||
..addFlag('visit-transitive-closure',
|
||||
help: 'Visit the transitive closure of imported/exported libraries.')
|
||||
..addFlag('quiet', abbr: 'q', help: "Don't show individual lint errors.")
|
||||
..addFlag('machine',
|
||||
help: 'Print results in a format suitable for parsing.',
|
||||
negatable: false)
|
||||
..addOption('config', abbr: 'c', help: 'Use configuration from this file.')
|
||||
..addOption('dart-sdk', help: 'Custom path to a Dart SDK.')
|
||||
..addMultiOption('rules',
|
||||
help: 'A list of lint rules to run. For example: '
|
||||
'annotate_overrides, avoid_catching_errors')
|
||||
// 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.');
|
||||
|
||||
ArgResults options;
|
||||
try {
|
||||
options = parser.parse(args);
|
||||
} on FormatException catch (err) {
|
||||
printUsage(parser, errorSink, err.message);
|
||||
exitCode = unableToProcessExitCode;
|
||||
return;
|
||||
}
|
||||
|
||||
if (options['help'] as bool) {
|
||||
printUsage(parser, outSink);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.rest.isEmpty) {
|
||||
printUsage(parser, errorSink,
|
||||
'Please provide at least one file or directory to lint.');
|
||||
exitCode = unableToProcessExitCode;
|
||||
return;
|
||||
}
|
||||
|
||||
var lintOptions = initialLintOptions;
|
||||
|
||||
var configFile = options['config'];
|
||||
if (configFile is String) {
|
||||
var config = LintConfig.parse(readFile(configFile));
|
||||
lintOptions.configure(config);
|
||||
}
|
||||
|
||||
var lints = options['rules'];
|
||||
if (lints is Iterable<String> && lints.isNotEmpty) {
|
||||
var rules = <LintRule>[];
|
||||
for (var lint in lints) {
|
||||
var rule = Registry.ruleRegistry[lint];
|
||||
if (rule == null) {
|
||||
errorSink.write('Unrecognized lint rule: $lint');
|
||||
exit(unableToProcessExitCode);
|
||||
}
|
||||
rules.add(rule);
|
||||
}
|
||||
|
||||
lintOptions.enabledLints = rules;
|
||||
}
|
||||
|
||||
var customSdk = options['dart-sdk'];
|
||||
if (customSdk is String) {
|
||||
lintOptions.dartSdkPath = customSdk;
|
||||
}
|
||||
|
||||
var packageConfigFile = options['packages'] as String?;
|
||||
packageConfigFile = packageConfigFile?.toAbsoluteNormalizedPath();
|
||||
|
||||
var stats = options['stats'] as bool;
|
||||
var benchmark = options['benchmark'] as bool;
|
||||
if (stats || benchmark) {
|
||||
lintOptions.enableTiming = true;
|
||||
}
|
||||
|
||||
lintOptions
|
||||
..packageConfigPath = packageConfigFile
|
||||
..resourceProvider = PhysicalResourceProvider.INSTANCE;
|
||||
|
||||
var filesToLint = <File>[];
|
||||
for (var path in options.rest) {
|
||||
filesToLint.addAll(
|
||||
collectFiles(path)
|
||||
.map((file) => file.path.toAbsoluteNormalizedPath())
|
||||
.map(File.new),
|
||||
);
|
||||
}
|
||||
|
||||
if (benchmark) {
|
||||
await writeBenchmarks(outSink, filesToLint, lintOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
var linter = DartLinter(lintOptions);
|
||||
|
||||
try {
|
||||
var timer = Stopwatch()..start();
|
||||
var errors = await lintFiles(linter, filesToLint);
|
||||
timer.stop();
|
||||
|
||||
var commonRoot = getRoot(options.rest);
|
||||
var machine = options['machine'] ?? false;
|
||||
var quiet = options['quiet'] ?? false;
|
||||
ReportFormatter(errors, lintOptions.filter, outSink,
|
||||
elapsedMs: timer.elapsedMilliseconds,
|
||||
fileCount: linter.numSourcesAnalyzed,
|
||||
fileRoot: commonRoot,
|
||||
showStatistics: stats,
|
||||
machineOutput: machine as bool,
|
||||
quiet: quiet as bool)
|
||||
.write();
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (err, stack) {
|
||||
errorSink.writeln('''An error occurred while linting
|
||||
Please report it at: github.com/dart-lang/linter/issues
|
||||
$err
|
||||
$stack''');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
// 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/constant/value.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/dart/element/member.dart'; // ignore: implementation_imports
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'analyzer.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>();
|
||||
|
||||
bool get isEffectivelyPrivate {
|
||||
var node = this;
|
||||
if (node.isInternal) return true;
|
||||
if (node is ClassDeclaration) {
|
||||
var classElement = node.declaredElement;
|
||||
if (classElement != null) {
|
||||
if (classElement.isSealed) return true;
|
||||
if (classElement.isAbstract) {
|
||||
if (classElement.isFinal) return true;
|
||||
if (classElement.isInterface) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get isInternal {
|
||||
var parent = thisOrAncestorOfType<CompilationUnitMember>();
|
||||
if (parent == null) return false;
|
||||
|
||||
var element = parent.declaredElement;
|
||||
return element != null && element.hasInternal;
|
||||
}
|
||||
|
||||
/// 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 AstNodeNullableExtension on AstNode? {
|
||||
bool get isFieldNameShortcut {
|
||||
var node = this;
|
||||
if (node is NullCheckPattern) node = node.parent;
|
||||
if (node is NullAssertPattern) node = node.parent;
|
||||
return node is PatternField && node.name != null && node.name?.name == null;
|
||||
}
|
||||
|
||||
/// Return `true` if the expression is null aware, or if one of its recursive
|
||||
/// targets is null aware.
|
||||
bool containsNullAwareInvocationInChain() {
|
||||
var node = this;
|
||||
if (node is PropertyAccess) {
|
||||
if (node.isNullAware) return true;
|
||||
return node.target.containsNullAwareInvocationInChain();
|
||||
} else if (node is MethodInvocation) {
|
||||
if (node.isNullAware) return true;
|
||||
return node.target.containsNullAwareInvocationInChain();
|
||||
} else if (node is IndexExpression) {
|
||||
if (node.isNullAware) return true;
|
||||
return node.target.containsNullAwareInvocationInChain();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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 typeToCheck = this;
|
||||
if (typeToCheck is TypeParameterType) {
|
||||
typeToCheck = typeToCheck.typeForInterfaceCheck;
|
||||
}
|
||||
if (typeToCheck is InterfaceType) {
|
||||
return isAnyInterface(typeToCheck) ||
|
||||
!typeToCheck.element.isSynthetic &&
|
||||
typeToCheck.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 FieldDeclarationExtension on FieldDeclaration {
|
||||
bool get isInvalidExtensionTypeField =>
|
||||
!isStatic && parent is ExtensionTypeDeclaration;
|
||||
}
|
||||
|
||||
extension InhertanceManager3Extension on InheritanceManager3 {
|
||||
/// Returns the class member that is overridden by [member], if there is one,
|
||||
/// as defined by [getInherited].
|
||||
ExecutableElement? overriddenMember(Element? member) {
|
||||
if (member == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var interfaceElement = member.thisOrAncestorOfType<InterfaceElement>();
|
||||
if (interfaceElement == null) {
|
||||
return null;
|
||||
}
|
||||
var name = member.name;
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var libraryUri = interfaceElement.library.source.uri;
|
||||
return getInherited(interfaceElement.thisType, Name(libraryUri, name));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
extension StringExtension on String {
|
||||
String toAbsoluteNormalizedPath() {
|
||||
var pathContext = PhysicalResourceProvider.INSTANCE.pathContext;
|
||||
return pathContext.normalize(pathContext.absolute(this));
|
||||
}
|
||||
}
|
||||
|
||||
extension TokenExtension on Token? {
|
||||
bool get isFinal => this?.keyword == Keyword.FINAL;
|
||||
}
|
||||
|
||||
extension TokenTypeExtension on TokenType {
|
||||
TokenType get inverted => switch (this) {
|
||||
TokenType.LT_EQ => TokenType.GT_EQ,
|
||||
TokenType.LT => TokenType.GT,
|
||||
TokenType.GT => TokenType.LT,
|
||||
TokenType.GT_EQ => TokenType.LT_EQ,
|
||||
_ => this
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
// 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 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:analyzer/error/error.dart';
|
||||
|
||||
import 'analyzer.dart';
|
||||
import 'util/charcodes.dart' show $backslash, $pipe;
|
||||
import 'util/score_utils.dart';
|
||||
|
||||
// Number of times to perform linting to get stable benchmarks.
|
||||
const benchmarkRuns = 10;
|
||||
|
||||
String getLineContents(int? lineNumber, AnalysisError error) {
|
||||
var path = error.source.fullName;
|
||||
var file = File(path);
|
||||
String failureDetails;
|
||||
if (!file.existsSync()) {
|
||||
failureDetails = 'file at $path does not exist';
|
||||
} else {
|
||||
var lines = file.readAsLinesSync();
|
||||
var lineIndex = lineNumber! - 1;
|
||||
if (lines.length > lineIndex) {
|
||||
return lines[lineIndex];
|
||||
}
|
||||
failureDetails =
|
||||
'line index ($lineIndex), outside of file line range (${lines.length})';
|
||||
}
|
||||
throw StateError('Unable to get contents for line: $failureDetails');
|
||||
}
|
||||
|
||||
String pluralize(String word, int? count) =>
|
||||
"$count ${count == 1 ? word : '${word}s'}";
|
||||
String shorten(String? fileRoot, String fullName) {
|
||||
if (fileRoot == null || !fullName.startsWith(fileRoot)) {
|
||||
return fullName;
|
||||
}
|
||||
return fullName.substring(fileRoot.length);
|
||||
}
|
||||
|
||||
Future writeBenchmarks(
|
||||
IOSink out, List<File> filesToLint, LinterOptions lintOptions) async {
|
||||
var timings = <String, int>{};
|
||||
for (var i = 0; i < benchmarkRuns; ++i) {
|
||||
await lintFiles(DartLinter(lintOptions), filesToLint);
|
||||
lintRegistry.timers.forEach((n, t) {
|
||||
var timing = t.elapsedMilliseconds;
|
||||
var previous = timings[n];
|
||||
if (previous == null) {
|
||||
timings[n] = timing;
|
||||
} else {
|
||||
timings[n] = min(previous, timing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var coreRuleset = await coreRules;
|
||||
var recommendedRuleset = await recommendedRules;
|
||||
var flutterRuleset = await flutterRules;
|
||||
|
||||
var stats = timings.keys.map((t) {
|
||||
var sets = <String>[];
|
||||
if (coreRuleset.contains(t)) {
|
||||
sets.add('core');
|
||||
}
|
||||
if (recommendedRuleset.contains(t)) {
|
||||
sets.add('recommended');
|
||||
}
|
||||
if (flutterRuleset.contains(t)) {
|
||||
sets.add('flutter');
|
||||
}
|
||||
|
||||
var details = sets.isEmpty ? '' : " [${sets.join(', ')}]";
|
||||
return _Stat('$t$details', timings[t] ?? 0);
|
||||
}).toList();
|
||||
_writeTimings(out, stats, 0);
|
||||
}
|
||||
|
||||
String _escapePipe(String input) {
|
||||
var result = StringBuffer();
|
||||
for (var c in input.codeUnits) {
|
||||
if (c == $backslash || c == $pipe) {
|
||||
result.write('\\');
|
||||
}
|
||||
result.writeCharCode(c);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
void _writeTimings(IOSink out, List<_Stat> timings, int summaryLength) {
|
||||
var names = timings.map((s) => s.name).toList();
|
||||
|
||||
var longestName =
|
||||
names.fold<int>(0, (prev, element) => max(prev, element.length));
|
||||
var longestTime = 8;
|
||||
var tableWidth = max(summaryLength, longestName + longestTime);
|
||||
var pad = tableWidth - longestName;
|
||||
var line = ''.padLeft(tableWidth, '-');
|
||||
|
||||
out
|
||||
..writeln()
|
||||
..writeln(line)
|
||||
..writeln('${'Timings'.padRight(longestName)}${'ms'.padLeft(pad)}')
|
||||
..writeln(line);
|
||||
var totalTime = 0;
|
||||
|
||||
timings.sort();
|
||||
for (var stat in timings) {
|
||||
totalTime += stat.elapsed;
|
||||
out.writeln(
|
||||
'${stat.name.padRight(longestName)}${stat.elapsed.toString().padLeft(pad)}');
|
||||
}
|
||||
out
|
||||
..writeln(line)
|
||||
..writeln(
|
||||
'${'Total'.padRight(longestName)}${totalTime.toString().padLeft(pad)}')
|
||||
..writeln(line);
|
||||
}
|
||||
|
||||
class DetailedReporter extends SimpleFormatter {
|
||||
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}) {
|
||||
super.writeLint(error, offset: offset, column: column, line: line);
|
||||
|
||||
if (!machineOutput) {
|
||||
var contents = getLineContents(line, error);
|
||||
out.writeln(contents);
|
||||
|
||||
var spaces = column! - 1;
|
||||
var arrows = max(1, min(error.length, contents.length - spaces));
|
||||
|
||||
var result = '${" " * spaces}${"^" * arrows}';
|
||||
out.writeln(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ReportFormatter {
|
||||
factory ReportFormatter(
|
||||
Iterable<AnalysisErrorInfo> errors, LintFilter? filter, IOSink out,
|
||||
{int? fileCount,
|
||||
int? elapsedMs,
|
||||
String? fileRoot,
|
||||
bool showStatistics = false,
|
||||
bool machineOutput = false,
|
||||
bool quiet = false}) =>
|
||||
DetailedReporter(errors, filter, out,
|
||||
fileCount: fileCount,
|
||||
fileRoot: fileRoot,
|
||||
elapsedMs: elapsedMs,
|
||||
showStatistics: showStatistics,
|
||||
machineOutput: machineOutput,
|
||||
quiet: quiet);
|
||||
|
||||
void write();
|
||||
}
|
||||
|
||||
/// Simple formatter suitable for subclassing.
|
||||
class SimpleFormatter implements ReportFormatter {
|
||||
final IOSink out;
|
||||
final Iterable<AnalysisErrorInfo> errors;
|
||||
final LintFilter? filter;
|
||||
|
||||
int errorCount = 0;
|
||||
int filteredLintCount = 0;
|
||||
|
||||
final int? fileCount;
|
||||
final int? elapsedMs;
|
||||
final String? fileRoot;
|
||||
final bool showStatistics;
|
||||
final bool machineOutput;
|
||||
final bool quiet;
|
||||
|
||||
/// Cached for the purposes of statistics report formatting.
|
||||
int _summaryLength = 0;
|
||||
|
||||
Map<String, int> stats = <String, int>{};
|
||||
|
||||
SimpleFormatter(this.errors, this.filter, this.out,
|
||||
{this.fileCount,
|
||||
this.fileRoot,
|
||||
this.elapsedMs,
|
||||
this.showStatistics = false,
|
||||
this.quiet = false,
|
||||
this.machineOutput = false});
|
||||
|
||||
/// Override to influence error sorting
|
||||
int compare(AnalysisError error1, AnalysisError error2) {
|
||||
// Severity
|
||||
var compare = error2.errorCode.errorSeverity
|
||||
.compareTo(error1.errorCode.errorSeverity);
|
||||
if (compare != 0) {
|
||||
return compare;
|
||||
}
|
||||
// Path
|
||||
compare = Comparable.compare(error1.source.fullName.toLowerCase(),
|
||||
error2.source.fullName.toLowerCase());
|
||||
if (compare != 0) {
|
||||
return compare;
|
||||
}
|
||||
// Offset
|
||||
return error1.offset - error2.offset;
|
||||
}
|
||||
|
||||
@override
|
||||
void write() {
|
||||
writeLints();
|
||||
writeSummary();
|
||||
if (showStatistics) {
|
||||
out.writeln();
|
||||
writeStatistics();
|
||||
}
|
||||
out.writeln();
|
||||
}
|
||||
|
||||
void writeCounts() {
|
||||
var codes = stats.keys.toList()..sort();
|
||||
var largestCountGuess = 8;
|
||||
var longest =
|
||||
codes.fold(0, (int prev, element) => max(prev, element.length));
|
||||
var tableWidth = max(_summaryLength, longest + largestCountGuess);
|
||||
var pad = tableWidth - longest;
|
||||
var line = ''.padLeft(tableWidth, '-');
|
||||
out
|
||||
..writeln(line)
|
||||
..writeln('Counts')
|
||||
..writeln(line);
|
||||
for (var code in codes) {
|
||||
out
|
||||
..write(code.padRight(longest))
|
||||
..writeln(stats[code].toString().padLeft(pad));
|
||||
}
|
||||
out.writeln(line);
|
||||
}
|
||||
|
||||
void writeLint(AnalysisError error, {int? offset, int? line, int? column}) {
|
||||
if (machineOutput) {
|
||||
//INFO|LINT|constant_identifier_names|test/engine_test.dart|91|22|3|Prefer using lowerCamelCase for constant names.
|
||||
out
|
||||
..write(error.errorCode.errorSeverity)
|
||||
..write('|')
|
||||
..write(error.errorCode.type)
|
||||
..write('|')
|
||||
..write(error.errorCode.name)
|
||||
..write('|')
|
||||
..write(_escapePipe(error.source.fullName))
|
||||
..write('|')
|
||||
..write(line)
|
||||
..write('|')
|
||||
..write(column)
|
||||
..write('|')
|
||||
..write(error.length)
|
||||
..write('|')
|
||||
..writeln(_escapePipe(error.message));
|
||||
} else {
|
||||
// test/engine_test.dart 452:9 [lint] DO name types using UpperCamelCase.
|
||||
out
|
||||
..write('${shorten(fileRoot, error.source.fullName)} ')
|
||||
..write('$line:$column ')
|
||||
..writeln('[${error.errorCode.type.displayName}] ${error.message}');
|
||||
}
|
||||
}
|
||||
|
||||
void writeLints() {
|
||||
var filter = this.filter;
|
||||
for (var info in errors) {
|
||||
for (var e in (info.errors.toList()..sort(compare))) {
|
||||
if (filter != null && filter.filter(e)) {
|
||||
filteredLintCount++;
|
||||
} else {
|
||||
++errorCount;
|
||||
if (!quiet) {
|
||||
_writeLint(e, info.lineInfo);
|
||||
}
|
||||
_recordStats(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!quiet) {
|
||||
out.writeln();
|
||||
}
|
||||
}
|
||||
|
||||
void writeStatistics() {
|
||||
writeCounts();
|
||||
writeTimings();
|
||||
}
|
||||
|
||||
void writeSummary() {
|
||||
var summary = '${pluralize("file", fileCount)} analyzed, '
|
||||
'${pluralize("issue", errorCount)} found'
|
||||
"${filteredLintCount == 0 ? '' : ' ($filteredLintCount filtered)'}, in $elapsedMs ms.";
|
||||
out.writeln(summary);
|
||||
// Cache for output table sizing
|
||||
_summaryLength = summary.length;
|
||||
}
|
||||
|
||||
void writeTimings() {
|
||||
var timers = lintRegistry.timers;
|
||||
var timings = timers.keys
|
||||
.map((t) => _Stat(t, timers[t]?.elapsedMilliseconds ?? 0))
|
||||
.toList();
|
||||
_writeTimings(out, timings, _summaryLength);
|
||||
}
|
||||
|
||||
void _recordStats(AnalysisError error) {
|
||||
var codeName = error.errorCode.name;
|
||||
stats.putIfAbsent(codeName, () => 0);
|
||||
stats[codeName] = stats[codeName]! + 1;
|
||||
}
|
||||
|
||||
void _writeLint(AnalysisError error, LineInfo lineInfo) {
|
||||
var offset = error.offset;
|
||||
var location = lineInfo.getLocation(offset);
|
||||
var line = location.lineNumber;
|
||||
var column = location.columnNumber;
|
||||
|
||||
writeLint(error, offset: offset, column: column, line: line);
|
||||
}
|
||||
}
|
||||
|
||||
class _Stat implements Comparable<_Stat> {
|
||||
final String name;
|
||||
final int elapsed;
|
||||
|
||||
_Stat(this.name, this.elapsed);
|
||||
|
||||
@override
|
||||
int compareTo(_Stat other) => other.elapsed - elapsed;
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
// 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 'analyzer.dart';
|
||||
import 'rules/always_declare_return_types.dart';
|
||||
import 'rules/always_put_control_body_on_new_line.dart';
|
||||
import 'rules/always_put_required_named_parameters_first.dart';
|
||||
import 'rules/always_require_non_null_named_parameters.dart';
|
||||
import 'rules/always_specify_types.dart';
|
||||
import 'rules/always_use_package_imports.dart';
|
||||
import 'rules/annotate_overrides.dart';
|
||||
import 'rules/avoid_annotating_with_dynamic.dart';
|
||||
import 'rules/avoid_as.dart';
|
||||
import 'rules/avoid_bool_literals_in_conditional_expressions.dart';
|
||||
import 'rules/avoid_catches_without_on_clauses.dart';
|
||||
import 'rules/avoid_catching_errors.dart';
|
||||
import 'rules/avoid_classes_with_only_static_members.dart';
|
||||
import 'rules/avoid_double_and_int_checks.dart';
|
||||
import 'rules/avoid_dynamic_calls.dart';
|
||||
import 'rules/avoid_empty_else.dart';
|
||||
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';
|
||||
import 'rules/avoid_js_rounded_ints.dart';
|
||||
import 'rules/avoid_multiple_declarations_per_line.dart';
|
||||
import 'rules/avoid_null_checks_in_equality_operators.dart';
|
||||
import 'rules/avoid_positional_boolean_parameters.dart';
|
||||
import 'rules/avoid_print.dart';
|
||||
import 'rules/avoid_private_typedef_functions.dart';
|
||||
import 'rules/avoid_redundant_argument_values.dart';
|
||||
import 'rules/avoid_relative_lib_imports.dart';
|
||||
import 'rules/avoid_renaming_method_parameters.dart';
|
||||
import 'rules/avoid_return_types_on_setters.dart';
|
||||
import 'rules/avoid_returning_null.dart';
|
||||
import 'rules/avoid_returning_null_for_future.dart';
|
||||
import 'rules/avoid_returning_null_for_void.dart';
|
||||
import 'rules/avoid_returning_this.dart';
|
||||
import 'rules/avoid_setters_without_getters.dart';
|
||||
import 'rules/avoid_shadowing_type_parameters.dart';
|
||||
import 'rules/avoid_single_cascade_in_expression_statements.dart';
|
||||
import 'rules/avoid_slow_async_io.dart';
|
||||
import 'rules/avoid_type_to_string.dart';
|
||||
import 'rules/avoid_types_as_parameter_names.dart';
|
||||
import 'rules/avoid_types_on_closure_parameters.dart';
|
||||
import 'rules/avoid_unnecessary_containers.dart';
|
||||
import 'rules/avoid_unused_constructor_parameters.dart';
|
||||
import 'rules/avoid_void_async.dart';
|
||||
import 'rules/avoid_web_libraries_in_flutter.dart';
|
||||
import 'rules/await_only_futures.dart';
|
||||
import 'rules/camel_case_extensions.dart';
|
||||
import 'rules/camel_case_types.dart';
|
||||
import 'rules/cancel_subscriptions.dart';
|
||||
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/deprecated_consistency.dart';
|
||||
import 'rules/deprecated_member_use_from_same_package.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/implicit_reopen.dart';
|
||||
import 'rules/invalid_case_patterns.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';
|
||||
import 'rules/lines_longer_than_80_chars.dart';
|
||||
import 'rules/list_remove_unrelated_type.dart';
|
||||
import 'rules/literal_only_boolean_expressions.dart';
|
||||
import 'rules/matching_super_parameters.dart';
|
||||
import 'rules/missing_whitespace_between_adjacent_strings.dart';
|
||||
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_literal_bool_comparisons.dart';
|
||||
import 'rules/no_logic_in_create_state.dart';
|
||||
import 'rules/no_runtimeType_toString.dart';
|
||||
import 'rules/no_self_assignments.dart';
|
||||
import 'rules/no_wildcard_variable_uses.dart';
|
||||
import 'rules/non_constant_identifier_names.dart';
|
||||
import 'rules/noop_primitive_operations.dart';
|
||||
import 'rules/null_check_on_nullable_type_parameter.dart';
|
||||
import 'rules/null_closures.dart';
|
||||
import 'rules/omit_local_variable_types.dart';
|
||||
import 'rules/one_member_abstracts.dart';
|
||||
import 'rules/only_throw_errors.dart';
|
||||
import 'rules/overridden_fields.dart';
|
||||
import 'rules/package_api_docs.dart';
|
||||
import 'rules/package_prefixed_library_names.dart';
|
||||
import 'rules/parameter_assignments.dart';
|
||||
import 'rules/prefer_adjacent_string_concatenation.dart';
|
||||
import 'rules/prefer_asserts_in_initializer_lists.dart';
|
||||
import 'rules/prefer_asserts_with_message.dart';
|
||||
import 'rules/prefer_bool_in_asserts.dart';
|
||||
import 'rules/prefer_collection_literals.dart';
|
||||
import 'rules/prefer_conditional_assignment.dart';
|
||||
import 'rules/prefer_const_constructors.dart';
|
||||
import 'rules/prefer_const_constructors_in_immutables.dart';
|
||||
import 'rules/prefer_const_declarations.dart';
|
||||
import 'rules/prefer_const_literals_to_create_immutables.dart';
|
||||
import 'rules/prefer_constructors_over_static_methods.dart';
|
||||
import 'rules/prefer_contains.dart';
|
||||
import 'rules/prefer_double_quotes.dart';
|
||||
import 'rules/prefer_equal_for_default_values.dart';
|
||||
import 'rules/prefer_expression_function_bodies.dart';
|
||||
import 'rules/prefer_final_fields.dart';
|
||||
import 'rules/prefer_final_in_for_each.dart';
|
||||
import 'rules/prefer_final_locals.dart';
|
||||
import 'rules/prefer_final_parameters.dart';
|
||||
import 'rules/prefer_for_elements_to_map_fromIterable.dart';
|
||||
import 'rules/prefer_foreach.dart';
|
||||
import 'rules/prefer_function_declarations_over_variables.dart';
|
||||
import 'rules/prefer_generic_function_type_aliases.dart';
|
||||
import 'rules/prefer_if_elements_to_conditional_expressions.dart';
|
||||
import 'rules/prefer_if_null_operators.dart';
|
||||
import 'rules/prefer_initializing_formals.dart';
|
||||
import 'rules/prefer_inlined_adds.dart';
|
||||
import 'rules/prefer_int_literals.dart';
|
||||
import 'rules/prefer_interpolation_to_compose_strings.dart';
|
||||
import 'rules/prefer_is_empty.dart';
|
||||
import 'rules/prefer_is_not_empty.dart';
|
||||
import 'rules/prefer_is_not_operator.dart';
|
||||
import 'rules/prefer_iterable_whereType.dart';
|
||||
import 'rules/prefer_mixin.dart';
|
||||
import 'rules/prefer_null_aware_method_calls.dart';
|
||||
import 'rules/prefer_null_aware_operators.dart';
|
||||
import 'rules/prefer_relative_imports.dart';
|
||||
import 'rules/prefer_single_quotes.dart';
|
||||
import 'rules/prefer_spread_collections.dart';
|
||||
import 'rules/prefer_typing_uninitialized_variables.dart';
|
||||
import 'rules/prefer_void_to_null.dart';
|
||||
import 'rules/provide_deprecation_message.dart';
|
||||
import 'rules/pub/depend_on_referenced_packages.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';
|
||||
import 'rules/sort_unnamed_constructors_first.dart';
|
||||
import 'rules/super_goes_last.dart';
|
||||
import 'rules/test_types_in_equals.dart';
|
||||
import 'rules/throw_in_finally.dart';
|
||||
import 'rules/tighten_type_of_initializing_formals.dart';
|
||||
import 'rules/type_annotate_public_apis.dart';
|
||||
import 'rules/type_init_formals.dart';
|
||||
import 'rules/type_literal_in_constant_pattern.dart';
|
||||
import 'rules/unawaited_futures.dart';
|
||||
import 'rules/unnecessary_await_in_return.dart';
|
||||
import 'rules/unnecessary_brace_in_string_interps.dart';
|
||||
import 'rules/unnecessary_breaks.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';
|
||||
import 'rules/unnecessary_overrides.dart';
|
||||
import 'rules/unnecessary_parenthesis.dart';
|
||||
import 'rules/unnecessary_raw_strings.dart';
|
||||
import 'rules/unnecessary_statements.dart';
|
||||
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';
|
||||
import 'rules/use_is_even_rather_than_modulo.dart';
|
||||
import 'rules/use_key_in_widget_constructors.dart';
|
||||
import 'rules/use_late_for_private_fields_and_variables.dart';
|
||||
import 'rules/use_named_constants.dart';
|
||||
import 'rules/use_raw_strings.dart';
|
||||
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';
|
||||
import 'rules/void_checks.dart';
|
||||
|
||||
void registerLintRules({bool inTestMode = false}) {
|
||||
Analyzer.facade.cacheLinterVersion();
|
||||
Analyzer.facade
|
||||
..register(AlwaysDeclareReturnTypes())
|
||||
..register(AlwaysPutControlBodyOnNewLine())
|
||||
..register(AlwaysPutRequiredNamedParametersFirst())
|
||||
..register(AlwaysRequireNonNullNamedParameters())
|
||||
..register(AlwaysSpecifyTypes())
|
||||
..register(AlwaysUsePackageImports())
|
||||
..register(AnnotateOverrides())
|
||||
..register(AvoidAnnotatingWithDynamic())
|
||||
..register(AvoidAs())
|
||||
..register(AvoidBoolLiteralsInConditionalExpressions())
|
||||
..register(AvoidCatchesWithoutOnClauses())
|
||||
..register(AvoidCatchingErrors())
|
||||
..register(AvoidClassesWithOnlyStaticMembers())
|
||||
..register(AvoidDoubleAndIntChecks())
|
||||
..register(AvoidDynamicCalls())
|
||||
..register(AvoidEmptyElse())
|
||||
..register(AvoidEqualsAndHashCodeOnMutableClasses())
|
||||
..register(AvoidEscapingInnerQuotes())
|
||||
..register(AvoidFieldInitializersInConstClasses())
|
||||
..register(AvoidFinalParameters())
|
||||
..register(AvoidFunctionLiteralsInForeachCalls())
|
||||
..register(AvoidImplementingValueTypes())
|
||||
..register(AvoidInitToNull())
|
||||
..register(AvoidJsRoundedInts())
|
||||
..register(AvoidMultipleDeclarationsPerLine())
|
||||
..register(AvoidNullChecksInEqualityOperators())
|
||||
..register(AvoidPositionalBooleanParameters())
|
||||
..register(AvoidPrint())
|
||||
..register(AvoidPrivateTypedefFunctions())
|
||||
..register(AvoidRedundantArgumentValues())
|
||||
..register(AvoidRelativeLibImports())
|
||||
..register(AvoidRenamingMethodParameters())
|
||||
..register(AvoidReturnTypesOnSetters())
|
||||
..register(AvoidReturningNull())
|
||||
..register(AvoidReturningNullForFuture())
|
||||
..register(AvoidReturningNullForVoid())
|
||||
..register(AvoidReturningThis())
|
||||
..register(AvoidSettersWithoutGetters())
|
||||
..register(AvoidShadowingTypeParameters())
|
||||
..register(AvoidSingleCascadeInExpressionStatements())
|
||||
..register(AvoidSlowAsyncIo())
|
||||
..register(AvoidTypeToString())
|
||||
..register(AvoidTypesAsParameterNames())
|
||||
..register(AvoidTypesOnClosureParameters())
|
||||
..register(AvoidUnnecessaryContainers())
|
||||
..register(AvoidUnusedConstructorParameters())
|
||||
..register(AvoidVoidAsync())
|
||||
..register(AvoidWebLibrariesInFlutter())
|
||||
..register(AwaitOnlyFutures())
|
||||
..register(CamelCaseExtensions())
|
||||
..register(CamelCaseTypes())
|
||||
..register(CancelSubscriptions())
|
||||
..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(DeprecatedMemberUseFromSamePackage())
|
||||
..register(DiagnosticDescribeAllProperties())
|
||||
..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(ImplicitReopen())
|
||||
..register(InvalidCasePatterns())
|
||||
..register(InvariantBooleans())
|
||||
..register(IterableContainsUnrelatedType())
|
||||
..register(JoinReturnWithAssignment())
|
||||
..register(LeadingNewlinesInMultilineStrings())
|
||||
..register(LibraryAnnotations())
|
||||
..register(LibraryNames())
|
||||
..register(LibraryPrefixes())
|
||||
..register(LibraryPrivateTypesInPublicApi())
|
||||
..register(LinesLongerThan80Chars())
|
||||
..register(ListRemoveUnrelatedType())
|
||||
..register(LiteralOnlyBooleanExpressions())
|
||||
..register(MatchingSuperParameters())
|
||||
..register(MissingWhitespaceBetweenAdjacentStrings())
|
||||
..register(NoAdjacentStringsInList())
|
||||
..register(NoDefaultCases())
|
||||
..register(NoDuplicateCaseValues())
|
||||
..register(NoLeadingUnderscoresForLibraryPrefixes())
|
||||
..register(NoLeadingUnderscoresForLocalIdentifiers())
|
||||
..register(NoLiteralBoolComparisons())
|
||||
..register(NoLogicInCreateState())
|
||||
..register(NoRuntimeTypeToString())
|
||||
..register(NoSelfAssignments())
|
||||
..register(NoWildcardVariableUses())
|
||||
..register(NonConstantIdentifierNames())
|
||||
..register(NoopPrimitiveOperations())
|
||||
..register(NullCheckOnNullableTypeParameter())
|
||||
..register(NullClosures())
|
||||
..register(OmitLocalVariableTypes())
|
||||
..register(OneMemberAbstracts())
|
||||
..register(OnlyThrowErrors())
|
||||
..register(OverriddenFields())
|
||||
..register(PackageApiDocs())
|
||||
..register(PackageNames())
|
||||
..register(PackagePrefixedLibraryNames())
|
||||
..register(ParameterAssignments())
|
||||
..register(PreferAdjacentStringConcatenation())
|
||||
..register(PreferAssertsInInitializerLists())
|
||||
..register(PreferAssertsWithMessage())
|
||||
..register(PreferBoolInAsserts())
|
||||
..register(PreferCollectionLiterals())
|
||||
..register(PreferConditionalAssignment())
|
||||
..register(PreferConstConstructors())
|
||||
..register(PreferConstConstructorsInImmutables())
|
||||
..register(PreferConstDeclarations())
|
||||
..register(PreferConstLiteralsToCreateImmutables())
|
||||
..register(PreferConstructorsOverStaticMethods())
|
||||
..register(PreferContains())
|
||||
..register(PreferDoubleQuotes())
|
||||
..register(PreferEqualForDefaultValues())
|
||||
..register(PreferExpressionFunctionBodies())
|
||||
..register(PreferFinalFields())
|
||||
..register(PreferFinalInForEach())
|
||||
..register(PreferFinalLocals())
|
||||
..register(PreferFinalParameters())
|
||||
..register(PreferForElementsToMapFromIterable())
|
||||
..register(PreferForeach())
|
||||
..register(PreferFunctionDeclarationsOverVariables())
|
||||
..register(PreferGenericFunctionTypeAliases())
|
||||
..register(PreferIfElementsToConditionalExpressions())
|
||||
..register(PreferIfNullOperators())
|
||||
..register(PreferInitializingFormals())
|
||||
..register(PreferInlinedAdds())
|
||||
..register(PreferIntLiterals())
|
||||
..register(PreferInterpolationToComposeStrings())
|
||||
..register(PreferIsEmpty())
|
||||
..register(PreferIsNotEmpty())
|
||||
..register(PreferIsNotOperator())
|
||||
..register(PreferIterableWhereType())
|
||||
..register(PreferMixin())
|
||||
..register(PreferNullAwareMethodCalls())
|
||||
..register(PreferNullAwareOperators())
|
||||
..register(PreferRelativeImports())
|
||||
..register(PreferSingleQuotes())
|
||||
..register(PreferSpreadCollections())
|
||||
..register(PreferTypingUninitializedVariables())
|
||||
..register(PreferVoidToNull())
|
||||
..register(ProvideDeprecationMessage())
|
||||
..register(PublicMemberApiDocs())
|
||||
..register(RecursiveGetters())
|
||||
..register(RequireTrailingCommas())
|
||||
..register(SecurePubspecUrls())
|
||||
..register(SizedBoxForWhitespace())
|
||||
..register(SizedBoxShrinkExpand())
|
||||
..register(SlashForDocComments())
|
||||
..register(SortChildPropertiesLast())
|
||||
..register(SortConstructorsFirst())
|
||||
..register(SortPubDependencies())
|
||||
..register(SortUnnamedConstructorsFirst())
|
||||
..register(SuperGoesLast())
|
||||
..register(TestTypesInEquals())
|
||||
..register(ThrowInFinally())
|
||||
..register(TightenTypeOfInitializingFormals())
|
||||
..register(TypeAnnotatePublicApis())
|
||||
..register(TypeInitFormals())
|
||||
..register(TypeLiteralInConstantPattern())
|
||||
..register(UnawaitedFutures())
|
||||
..register(UnnecessaryAwaitInReturn())
|
||||
..register(UnnecessaryBraceInStringInterps())
|
||||
..register(UnnecessaryBreaks())
|
||||
..register(UnnecessaryConst())
|
||||
..register(UnnecessaryConstructorName())
|
||||
..register(UnnecessaryFinal())
|
||||
..register(UnnecessaryGettersSetters())
|
||||
..register(UnnecessaryLambdas())
|
||||
..register(UnnecessaryLate())
|
||||
..register(UnnecessaryLibraryDirective())
|
||||
..register(UnnecessaryNew())
|
||||
..register(UnnecessaryNullAwareAssignments())
|
||||
..register(UnnecessaryNullAwareOperatorOnExtensionOnNullable())
|
||||
..register(UnnecessaryNullChecks())
|
||||
..register(UnnecessaryNullInIfNullOperators())
|
||||
..register(UnnecessaryNullableForFinalVariableDeclarations())
|
||||
..register(UnnecessaryOverrides())
|
||||
..register(UnnecessaryParenthesis())
|
||||
..register(UnnecessaryRawStrings())
|
||||
..register(UnnecessaryStatements())
|
||||
..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())
|
||||
..register(UseIsEvenRatherThanModulo())
|
||||
..register(UseKeyInWidgetConstructors())
|
||||
..register(UseLateForPrivateFieldsAndVariables())
|
||||
..register(UseNamedConstants())
|
||||
..register(UseRawStrings())
|
||||
..register(UseRethrowWhenPossible())
|
||||
..register(UseSettersToChangeProperties())
|
||||
..register(UseStringBuffers())
|
||||
..register(UseStringInPartOfDirectives())
|
||||
..register(UseSuperParameters())
|
||||
..register(UseTestThrowsMatchers())
|
||||
..register(UseToAndAsIfApplicable())
|
||||
..register(ValidRegexps())
|
||||
..register(VoidChecks());
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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: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'Declare method return types.';
|
||||
|
||||
const _details = r'''
|
||||
**DO** declare method return types.
|
||||
|
||||
When declaring a method or function *always* specify a return type.
|
||||
Declaring return types for functions helps improve your codebase by allowing the
|
||||
analyzer to more adequately check your code for errors that could occur during
|
||||
runtime.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
main() { }
|
||||
|
||||
_bar() => _Foo();
|
||||
|
||||
class _Foo {
|
||||
_foo() => 42;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void main() { }
|
||||
|
||||
_Foo _bar() => _Foo();
|
||||
|
||||
class _Foo {
|
||||
int _foo() => 42;
|
||||
}
|
||||
|
||||
typedef predicate = bool Function(Object o);
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@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 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.",
|
||||
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.",
|
||||
correctionMessage:
|
||||
"Try adding a return type to the method."); // ignore: prefer_single_quotes
|
||||
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (!node.isSetter && node.returnType == null) {
|
||||
rule.reportLintForToken(node.name,
|
||||
arguments: [node.name.lexeme], errorCode: functionCode);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
||||
if (node.returnType == null) {
|
||||
rule.reportLintForToken(node.name,
|
||||
arguments: [node.name.lexeme], errorCode: functionCode);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (!node.isSetter &&
|
||||
node.returnType == null &&
|
||||
node.name.type != TokenType.INDEX_EQ) {
|
||||
rule.reportLintForToken(node.name,
|
||||
arguments: [node.name.lexeme], errorCode: methodCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2017, 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'Separate the control structure expression from its statement.';
|
||||
|
||||
const _details = r'''
|
||||
From the [style guide for the flutter repo](https://flutter.dev/style-guide/):
|
||||
|
||||
**DO** separate the control structure expression from its statement.
|
||||
|
||||
Don't put the statement part of an `if`, `for`, `while`, `do` on the same line
|
||||
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)
|
||||
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 {
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addDoStatement(this, visitor);
|
||||
registry.addForStatement(this, visitor);
|
||||
registry.addIfStatement(this, visitor);
|
||||
registry.addWhileStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitDoStatement(DoStatement node) {
|
||||
_checkNodeOnNextLine(node.body, node.doKeyword.end);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForStatement(ForStatement node) {
|
||||
_checkNodeOnNextLine(node.body, node.rightParenthesis.end);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
_checkNodeOnNextLine(node.thenStatement, node.rightParenthesis.end);
|
||||
var elseKeyword = node.elseKeyword;
|
||||
var elseStatement = node.elseStatement;
|
||||
if (elseKeyword != null && elseStatement is! IfStatement) {
|
||||
_checkNodeOnNextLine(elseStatement, elseKeyword.end);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitWhileStatement(WhileStatement node) {
|
||||
_checkNodeOnNextLine(node.body, node.rightParenthesis.end);
|
||||
}
|
||||
|
||||
void _checkNodeOnNextLine(AstNode? node, int controlEnd) {
|
||||
if (node == null || node is Block && node.statements.isEmpty) return;
|
||||
|
||||
var unit = node.root as CompilationUnit;
|
||||
var offsetFirstStatement =
|
||||
node is Block ? node.statements.first.offset : node.offset;
|
||||
var lineInfo = unit.lineInfo;
|
||||
if (lineInfo.getLocation(controlEnd).lineNumber ==
|
||||
lineInfo.getLocation(offsetFirstStatement).lineNumber) {
|
||||
rule.reportLintForToken(node.beginToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2017, 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'Put required named parameters first.';
|
||||
|
||||
const _details = r'''
|
||||
**DO** specify `required` on named parameter before other named parameters.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
m({b, c, required a}) ;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
m({required a, b, c}) ;
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
m({b, c, @required a}) ;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
m({@required a, b, c}) ;
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFormalParameterList(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFormalParameterList(FormalParameterList node) {
|
||||
var nonRequiredSeen = false;
|
||||
for (var param in node.parameters.where((p) => p.isNamed)) {
|
||||
var element = param.declaredElement;
|
||||
if (element != null && (element.hasRequired || element.isRequiredNamed)) {
|
||||
if (nonRequiredSeen) {
|
||||
var name = param.name;
|
||||
if (name != null) {
|
||||
rule.reportLintForToken(name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nonRequiredSeen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) 2017, 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/token.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.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}) {
|
||||
assert(a != null);
|
||||
}
|
||||
|
||||
m2({a: 1}) {
|
||||
assert(a != null);
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: Only asserts at the start of the bodies will be taken into account.
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
state: State.deprecated(since: dart2_12),
|
||||
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
|
||||
// analysis.
|
||||
if (!context.isEnabled(Feature.non_nullable)) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFormalParameterList(this, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFormalParameterList(FormalParameterList node) {
|
||||
List<DefaultFormalParameter> getParams() {
|
||||
var params = <DefaultFormalParameter>[];
|
||||
for (var p in node.parameters) {
|
||||
// Only named parameters
|
||||
if (p.isNamed) {
|
||||
var parameter = p as DefaultFormalParameter;
|
||||
// Without a default value or marked required
|
||||
if (parameter.defaultValue == null) {
|
||||
var declaredElement = parameter.declaredElement;
|
||||
if (declaredElement != null && !declaredElement.hasRequired) {
|
||||
params.add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
var parent = node.parent;
|
||||
if (parent is FunctionExpression) {
|
||||
_checkParams(getParams(), parent.body);
|
||||
} else if (parent is ConstructorDeclaration) {
|
||||
_checkInitializerList(getParams(), parent.initializers);
|
||||
_checkParams(getParams(), parent.body);
|
||||
} else if (parent is MethodDeclaration) {
|
||||
_checkParams(getParams(), parent.body);
|
||||
}
|
||||
}
|
||||
|
||||
void _checkAssert(
|
||||
Expression assertExpression, List<DefaultFormalParameter> params) {
|
||||
for (var param in params) {
|
||||
var name = param.name;
|
||||
if (name != null && _hasAssertNotNull(assertExpression, name.lexeme)) {
|
||||
rule.reportLintForToken(name);
|
||||
params.remove(param);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkInitializerList(List<DefaultFormalParameter> params,
|
||||
NodeList<ConstructorInitializer> initializers) {
|
||||
for (var initializer in initializers) {
|
||||
if (initializer is AssertInitializer) {
|
||||
_checkAssert(initializer.condition, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkParams(List<DefaultFormalParameter> params, FunctionBody body) {
|
||||
if (body is BlockFunctionBody) {
|
||||
for (var statement in body.block.statements) {
|
||||
if (statement is AssertStatement) {
|
||||
_checkAssert(statement.condition, params);
|
||||
} else {
|
||||
// Bail on first non-assert.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasAssertNotNull(Expression node, String name) {
|
||||
bool hasSameName(Expression rawExpression) {
|
||||
var expression = rawExpression.unParenthesized;
|
||||
return expression is SimpleIdentifier && expression.name == name;
|
||||
}
|
||||
|
||||
var expression = node.unParenthesized;
|
||||
if (expression is BinaryExpression) {
|
||||
if (expression.operator.type == TokenType.AMPERSAND_AMPERSAND) {
|
||||
return _hasAssertNotNull(expression.leftOperand, name) ||
|
||||
_hasAssertNotNull(expression.rightOperand, name);
|
||||
}
|
||||
if (expression.operator.type == TokenType.BANG_EQ) {
|
||||
var operands = [expression.leftOperand, expression.rightOperand];
|
||||
return operands.any((e) => e.isNullLiteral) &&
|
||||
operands.any(hasSameName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// 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: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 'package:analyzer/error/error.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../util/ascii_utils.dart';
|
||||
|
||||
const _desc = r'Specify type annotations.';
|
||||
|
||||
const _details = r'''
|
||||
From the [style guide for the flutter repo](https://flutter.dev/style-guide/):
|
||||
|
||||
**DO** specify type annotations.
|
||||
|
||||
Avoid `var` when specifying that a type is unknown and short-hands that elide
|
||||
type annotations. Use `dynamic` if you are being explicit that the type is
|
||||
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;
|
||||
final Bar bar = Bar();
|
||||
String baz = 'hello';
|
||||
const int 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
|
||||
`Key` object whose type parameter you'd like to treat as optional. Using the
|
||||
`@optionalTypeArgs` would look like this:
|
||||
|
||||
```dart
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@optionalTypeArgs
|
||||
class Key<T> {
|
||||
...
|
||||
}
|
||||
|
||||
main() {
|
||||
Key s = Key(); // OK!
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
class AlwaysSpecifyTypes extends LintRule {
|
||||
static const LintCode code = LintCode(
|
||||
'always_specify_types', 'Missing type annotation.',
|
||||
correctionMessage: 'Try adding a type annotation.');
|
||||
|
||||
AlwaysSpecifyTypes()
|
||||
: super(
|
||||
name: 'always_specify_types',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
List<String> get incompatibleRules =>
|
||||
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);
|
||||
registry.addDeclaredIdentifier(this, visitor);
|
||||
registry.addListLiteral(this, visitor);
|
||||
registry.addSetOrMapLiteral(this, visitor);
|
||||
registry.addSimpleFormalParameter(this, visitor);
|
||||
registry.addNamedType(this, visitor);
|
||||
registry.addDeclaredVariablePattern(this, visitor);
|
||||
registry.addVariableDeclarationList(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
static const LintCode keywordCouldBeTypeCode = LintCode(
|
||||
'always_specify_types',
|
||||
"Missing type annotation.", // ignore: prefer_single_quotes
|
||||
correctionMessage: "Try replacing '{0}' with '{1}'.");
|
||||
|
||||
static const LintCode keywordCouldBeSplitToTypesCode = LintCode(
|
||||
'always_specify_types',
|
||||
"Missing type annotation.", // ignore: prefer_single_quotes
|
||||
correctionMessage:
|
||||
"Try splitting the declaration and specify the different type annotations."); // ignore: prefer_single_quotes
|
||||
|
||||
static const LintCode specifyTypeCode = LintCode('always_specify_types',
|
||||
"Missing type annotation.", // ignore: prefer_single_quotes
|
||||
correctionMessage: "Try specifying the type '{0}'.");
|
||||
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
void checkLiteral(TypedLiteral literal) {
|
||||
if (literal.typeArguments == null) {
|
||||
rule.reportLintForToken(literal.beginToken);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDeclaredIdentifier(DeclaredIdentifier node) {
|
||||
var keyword = node.keyword;
|
||||
if (node.type == null && keyword != null) {
|
||||
var element = node.declaredElement;
|
||||
if (element is VariableElement) {
|
||||
if (keyword.keyword == Keyword.VAR) {
|
||||
rule.reportLintForToken(keyword,
|
||||
arguments: [keyword.lexeme, element!.type],
|
||||
errorCode: keywordCouldBeTypeCode);
|
||||
} else {
|
||||
rule.reportLintForToken(keyword,
|
||||
arguments: [element!.type], errorCode: specifyTypeCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
|
||||
if (node.type == null) {
|
||||
var type = node.matchedValueType!;
|
||||
var keyword = node.keyword;
|
||||
var tokenToLint = keyword ?? node.name;
|
||||
if (keyword != null && keyword.keyword == Keyword.VAR) {
|
||||
rule.reportLintForToken(tokenToLint,
|
||||
arguments: [keyword.lexeme, type],
|
||||
errorCode: keywordCouldBeTypeCode);
|
||||
} else {
|
||||
rule.reportLintForToken(tokenToLint,
|
||||
arguments: [type], errorCode: specifyTypeCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitListLiteral(ListLiteral literal) {
|
||||
checkLiteral(literal);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitNamedType(NamedType namedType) {
|
||||
var type = namedType.type;
|
||||
if (type is InterfaceType) {
|
||||
var element = namedType.element;
|
||||
if (element is TypeParameterizedElement &&
|
||||
element.typeParameters.isNotEmpty &&
|
||||
namedType.typeArguments == null &&
|
||||
namedType.parent is! IsExpression &&
|
||||
!element.hasOptionalTypeArgs) {
|
||||
rule.reportLint(namedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSetOrMapLiteral(SetOrMapLiteral literal) {
|
||||
checkLiteral(literal);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleFormalParameter(SimpleFormalParameter param) {
|
||||
var name = param.name;
|
||||
if (name != null && param.type == null && !name.lexeme.isJustUnderscores) {
|
||||
var keyword = param.keyword;
|
||||
if (keyword != null) {
|
||||
var type = param.declaredElement?.type;
|
||||
|
||||
if (keyword.type == Keyword.VAR &&
|
||||
type != null &&
|
||||
type is! DynamicType) {
|
||||
rule.reportLintForToken(keyword,
|
||||
arguments: [keyword.lexeme, type],
|
||||
errorCode: keywordCouldBeTypeCode);
|
||||
} else {
|
||||
rule.reportLintForToken(keyword);
|
||||
}
|
||||
} else if (param.declaredElement != null) {
|
||||
var type = param.declaredElement!.type;
|
||||
|
||||
if (type is DynamicType) {
|
||||
rule.reportLint(param);
|
||||
} else {
|
||||
rule.reportLint(param, arguments: [type], errorCode: specifyTypeCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitVariableDeclarationList(VariableDeclarationList list) {
|
||||
var keyword = list.keyword;
|
||||
if (list.type == null && keyword != null) {
|
||||
Set<String> types;
|
||||
var parent = list.parent;
|
||||
if (parent is TopLevelVariableDeclaration) {
|
||||
types = _getTypes(parent.variables);
|
||||
} else if (parent is ForPartsWithDeclarations) {
|
||||
types = _getTypes(parent.variables);
|
||||
} else if (parent is FieldDeclaration) {
|
||||
types = _getTypes(parent.fields);
|
||||
} else if (parent is VariableDeclarationStatement) {
|
||||
types = _getTypes(parent.variables);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var singleType = types.length == 1;
|
||||
|
||||
List<Object> arguments;
|
||||
ErrorCode? errorCode;
|
||||
if (types.isEmpty) {
|
||||
arguments = [];
|
||||
} else if (keyword.type == Keyword.VAR) {
|
||||
if (singleType) {
|
||||
arguments = [keyword.lexeme, types.first];
|
||||
errorCode = keywordCouldBeTypeCode;
|
||||
} else {
|
||||
arguments = [];
|
||||
errorCode = keywordCouldBeSplitToTypesCode;
|
||||
}
|
||||
} else {
|
||||
if (singleType) {
|
||||
arguments = [types.first];
|
||||
errorCode = specifyTypeCode;
|
||||
} else {
|
||||
arguments = [];
|
||||
}
|
||||
}
|
||||
rule.reportLintForToken(keyword,
|
||||
arguments: arguments, errorCode: errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> _getTypes(VariableDeclarationList list) {
|
||||
var types = <String>{};
|
||||
for (var variable in list.variables) {
|
||||
var initializer = variable.initializer;
|
||||
if (initializer != null) {
|
||||
DartType? type;
|
||||
if (initializer is Identifier) {
|
||||
var staticElement = initializer.staticElement;
|
||||
if (staticElement is VariableElement) {
|
||||
type = staticElement.type;
|
||||
}
|
||||
}
|
||||
|
||||
type ??= variable.initializer?.staticType;
|
||||
|
||||
if (type != null) {
|
||||
types.add(type.getDisplayString(withNullability: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../ast.dart';
|
||||
|
||||
const _desc = r'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 within the
|
||||
`lib/` directory.
|
||||
|
||||
This is the opposite of 'prefer_relative_imports'.
|
||||
|
||||
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';
|
||||
|
||||
import 'src/bag.dart'
|
||||
|
||||
import '../lib/baz.dart';
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'package:foo/bar.dart';
|
||||
|
||||
import 'package:foo/baz.dart';
|
||||
|
||||
import 'package:foo/src/baz.dart';
|
||||
...
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
List<String> get incompatibleRules => const ['prefer_relative_imports'];
|
||||
|
||||
@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);
|
||||
registry.addImportDirective(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
bool isRelativeImport(ImportDirective node) {
|
||||
var uriContent = node.uri.stringValue;
|
||||
if (uriContent != null) {
|
||||
var uri = Uri.tryParse(uriContent);
|
||||
return uri != null && uri.scheme.isEmpty;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitImportDirective(ImportDirective node) {
|
||||
if (isRelativeImport(node)) {
|
||||
rule.reportLint(node.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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.
|
||||
|
||||
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 '../extensions.dart';
|
||||
|
||||
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 {
|
||||
String get breed;
|
||||
void bark() {}
|
||||
}
|
||||
|
||||
class Husky extends Dog {
|
||||
@override
|
||||
final String breed = 'Husky';
|
||||
@override
|
||||
void bark() {}
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addFieldDeclaration(this, visitor);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
final LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
void check(Element? element, Token target) {
|
||||
if (element == null || element.hasOverride) return;
|
||||
|
||||
var member = context.inheritanceManager.overriddenMember(element);
|
||||
if (member != null) {
|
||||
rule.reportLintForToken(target, arguments: [member.name]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldDeclaration(FieldDeclaration node) {
|
||||
if (node.isStatic) return;
|
||||
|
||||
for (var field in node.fields.variables) {
|
||||
check(field.declaredElement, field.name);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (node.isStatic) return;
|
||||
|
||||
check(node.declaredElement, node.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2017, 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/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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
|
||||
not necessary to annotate it.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
dynamic lookUpOrDefault(String name, Map map, dynamic defaultValue) {
|
||||
var value = map[name];
|
||||
if (value != null) return value;
|
||||
return defaultValue;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
lookUpOrDefault(String name, Map map, defaultValue) {
|
||||
var value = map[name];
|
||||
if (value != null) return value;
|
||||
return defaultValue;
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFieldFormalParameter(FieldFormalParameter node) {
|
||||
_checkNode(node, node.type);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleFormalParameter(SimpleFormalParameter node) {
|
||||
_checkNode(node, node.type);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSuperFormalParameter(SuperFormalParameter node) {
|
||||
_checkNode(node, node.type);
|
||||
}
|
||||
|
||||
void _checkNode(NormalFormalParameter node, TypeAnnotation? type) {
|
||||
if (type is NamedType && type.type is DynamicType) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 '../analyzer.dart';
|
||||
|
||||
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
|
||||
narrowly-typed variable (this avoids the type check in release mode; `as` is not
|
||||
compiled out in release mode). If you don't know whether the type is
|
||||
correct, check using `is` (this avoids the exception that `as` raises).
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
(pm as Person).firstName = 'Seth';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
if (pm is Person)
|
||||
pm.firstName = 'Seth';
|
||||
```
|
||||
|
||||
but certainly not
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
try {
|
||||
(pm as Person).firstName = 'Seth';
|
||||
} on CastError { }
|
||||
```
|
||||
|
||||
Note that an exception is made in the case of `dynamic` since the cast has no
|
||||
performance impact.
|
||||
|
||||
**OK:**
|
||||
```dart
|
||||
HasScrollDirection scrollable = renderObject as dynamic;
|
||||
```
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style,
|
||||
state: State.removed(since: dart2_12),
|
||||
);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc = r'Avoid bool literals in conditional expressions.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** bool literals in conditional expressions.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
condition ? true : boolExpression
|
||||
condition ? false : boolExpression
|
||||
condition ? boolExpression : true
|
||||
condition ? boolExpression : false
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
condition || boolExpression
|
||||
!condition && boolExpression
|
||||
!condition || boolExpression
|
||||
condition && boolExpression
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addConditionalExpression(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
final LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
@override
|
||||
void visitConditionalExpression(ConditionalExpression node) {
|
||||
var typeProvider = context.typeProvider;
|
||||
var thenExp = node.thenExpression;
|
||||
var elseExp = node.elseExpression;
|
||||
|
||||
if (thenExp.staticType == typeProvider.boolType &&
|
||||
elseExp.staticType == typeProvider.boolType) {
|
||||
if (thenExp is BooleanLiteral) rule.reportLint(node);
|
||||
if (elseExp is BooleanLiteral) rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2017, 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 catches without on clauses.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** catches without on clauses.
|
||||
|
||||
Using catch clauses without on clauses make your code prone to encountering
|
||||
unexpected errors that won't be thrown (and thus will go unnoticed).
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
try {
|
||||
somethingRisky()
|
||||
}
|
||||
catch(e) {
|
||||
doSomething(e);
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
try {
|
||||
somethingRisky()
|
||||
}
|
||||
on Exception catch(e) {
|
||||
doSomething(e);
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitCatchClause(CatchClause node) {
|
||||
if (node.onKeyword == null) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2017, 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"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
|
||||
to runtime. It should almost never be necessary to catch an error at runtime.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
try {
|
||||
somethingRisky();
|
||||
} on Error catch(e) {
|
||||
doSomething(e);
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
try {
|
||||
somethingRisky();
|
||||
} on Exception catch(e) {
|
||||
doSomething(e);
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
List<LintCode> get lintCodes => [classCode, subclassCode];
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addCatchClause(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitCatchClause(CatchClause node) {
|
||||
var exceptionType = node.exceptionType?.type;
|
||||
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)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2017, 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'Avoid defining a class that contains only static members.';
|
||||
|
||||
const _details = r'''
|
||||
From [Effective Dart](https://dart.dev/effective-dart/design#avoid-defining-a-class-that-contains-only-static-members):
|
||||
|
||||
**AVOID** defining a class that contains only static members.
|
||||
|
||||
Creating classes with the sole purpose of providing utility or otherwise static
|
||||
methods is discouraged. Dart allows functions to exist outside of classes for
|
||||
this very reason.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class DateUtils {
|
||||
static DateTime mostRecent(List<DateTime> dates) {
|
||||
return dates.reduce((a, b) => a.isAfter(b) ? a : b);
|
||||
}
|
||||
}
|
||||
|
||||
class _Favorites {
|
||||
static const mammal = 'weasel';
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
DateTime mostRecent(List<DateTime> dates) {
|
||||
return dates.reduce((a, b) => a.isAfter(b) ? a : b);
|
||||
}
|
||||
|
||||
const _favoriteMammal = 'weasel';
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
bool _isNonConst(FieldElement element) => !element.isConst;
|
||||
|
||||
bool _isStaticMember(ClassMember classMember) {
|
||||
if (classMember is MethodDeclaration) {
|
||||
return classMember.isStatic;
|
||||
}
|
||||
if (classMember is FieldDeclaration) {
|
||||
return classMember.isStatic;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addClassDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var interface = context.inheritanceManager.getInterface(declaredElement);
|
||||
var map = interface.map;
|
||||
for (var member in map.values) {
|
||||
var enclosingElement = member.enclosingElement;
|
||||
if (enclosingElement is ClassElement &&
|
||||
!enclosingElement.isDartCoreObject) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.members.isNotEmpty &&
|
||||
node.members.every(_isStaticMember) &&
|
||||
(declaredElement.methods.isNotEmpty ||
|
||||
declaredElement.fields.any(_isNonConst))) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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.
|
||||
|
||||
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'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
|
||||
some unexpected behavior when using either `is` or `is!` where the type is
|
||||
either `int` or `double`.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
f(num x) {
|
||||
if (x is double) {
|
||||
...
|
||||
} else if (x is int) {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
f(dynamic x) {
|
||||
if (x is num) {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
class AvoidDoubleAndIntChecks extends LintRule {
|
||||
static const LintCode code = LintCode(
|
||||
'avoid_double_and_int_checks', 'Explicit check for double or int.',
|
||||
correctionMessage: 'Try removing the check.');
|
||||
|
||||
AvoidDoubleAndIntChecks()
|
||||
: super(
|
||||
name: 'avoid_double_and_int_checks',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addIfStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
final LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
var elseStatement = node.elseStatement;
|
||||
if (elseStatement is IfStatement) {
|
||||
var ifCondition = node.expression;
|
||||
var elseCondition = elseStatement.expression;
|
||||
if (ifCondition is IsExpression && elseCondition is IsExpression) {
|
||||
var typeProvider = context.typeProvider;
|
||||
var ifExpression = ifCondition.expression;
|
||||
var elseIsExpression = elseCondition.expression;
|
||||
if (ifExpression is SimpleIdentifier &&
|
||||
elseIsExpression is SimpleIdentifier &&
|
||||
ifExpression.name == elseIsExpression.name &&
|
||||
ifCondition.type.type == typeProvider.doubleType &&
|
||||
elseCondition.type.type == typeProvider.intType &&
|
||||
(ifExpression.staticElement is ParameterElement ||
|
||||
ifExpression.staticElement is LocalVariableElement)) {
|
||||
rule.reportLint(elseCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// 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 'package:analyzer/dart/element/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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
|
||||
production modes (and even some development modes) have both compile size and
|
||||
runtime performance penalties associated with dynamic calls.
|
||||
|
||||
Additionally, targets typed "dynamic" disables most static analysis, meaning it
|
||||
is easier to lead to a runtime "NoSuchMethodError" or "NullError" than properly
|
||||
statically typed Dart code.
|
||||
|
||||
There is an exception to methods and properties that exist on "Object?":
|
||||
- a.hashCode
|
||||
- a.runtimeType
|
||||
- a.noSuchMethod(someInvocation)
|
||||
- a.toString()
|
||||
|
||||
... these members are dynamically dispatched in the web-based runtimes, but not
|
||||
in the VM-based ones. Additionally, they are so common that it would be very
|
||||
punishing to disallow `any.toString()` or `any == true`, for example.
|
||||
|
||||
Note that despite "Function" being a type, the semantics are close to identical
|
||||
to "dynamic", and calls to an object that is typed "Function" will also trigger
|
||||
this lint.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void explicitDynamicType(dynamic object) {
|
||||
print(object.foo());
|
||||
}
|
||||
|
||||
void implicitDynamicType(object) {
|
||||
print(object.foo());
|
||||
}
|
||||
|
||||
abstract class SomeWrapper {
|
||||
T doSomething<T>();
|
||||
}
|
||||
|
||||
void inferredDynamicType(SomeWrapper wrapper) {
|
||||
var object = wrapper.doSomething();
|
||||
print(object.foo());
|
||||
}
|
||||
|
||||
void callDynamic(dynamic function) {
|
||||
function();
|
||||
}
|
||||
|
||||
void functionType(Function function) {
|
||||
function();
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void explicitType(Fooable object) {
|
||||
object.foo();
|
||||
}
|
||||
|
||||
void castedType(dynamic object) {
|
||||
(object as Fooable).foo();
|
||||
}
|
||||
|
||||
abstract class SomeWrapper {
|
||||
T doSomething<T>();
|
||||
}
|
||||
|
||||
void inferredType(SomeWrapper wrapper) {
|
||||
var object = wrapper.doSomething<Fooable>();
|
||||
object.foo();
|
||||
}
|
||||
|
||||
void functionTypeWithParameters(Function() function) {
|
||||
function();
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry,
|
||||
LinterContext context,
|
||||
) {
|
||||
var visitor = _Visitor(this);
|
||||
registry
|
||||
..addAssignmentExpression(this, visitor)
|
||||
..addBinaryExpression(this, visitor)
|
||||
..addFunctionExpressionInvocation(this, visitor)
|
||||
..addIndexExpression(this, visitor)
|
||||
..addMethodInvocation(this, visitor)
|
||||
..addPostfixExpression(this, visitor)
|
||||
..addPrefixExpression(this, visitor)
|
||||
..addPrefixedIdentifier(this, visitor)
|
||||
..addPropertyAccess(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitAssignmentExpression(AssignmentExpression node) {
|
||||
if (node.readType is! DynamicType) {
|
||||
// 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 not
|
||||
// dynamic, we don't need to check further.
|
||||
return;
|
||||
}
|
||||
if (node.operator.type == TokenType.QUESTION_QUESTION_EQ) {
|
||||
// x ??= foo is not a dynamic call.
|
||||
return;
|
||||
}
|
||||
rule.reportLint(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBinaryExpression(BinaryExpression node) {
|
||||
if (!node.operator.isUserDefinableOperator) {
|
||||
// Operators that can never be provided by the user can't be dynamic.
|
||||
return;
|
||||
}
|
||||
switch (node.operator.type) {
|
||||
case TokenType.EQ_EQ:
|
||||
case TokenType.BANG_EQ:
|
||||
// These operators exist on every type, even "Object?". While they are
|
||||
// virtually dispatched, they are not considered dynamic calls by the
|
||||
// CFE. They would also make landing this lint exponentially harder.
|
||||
return;
|
||||
}
|
||||
_lintIfDynamic(node.leftOperand);
|
||||
// We don't check node.rightOperand, because that is an implicit cast, not a
|
||||
// dynamic call (the call itself is based on leftOperand). While it would be
|
||||
// useful to do so, it is better solved by other more specific lints to
|
||||
// disallow implicit casts from dynamic.
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
|
||||
_lintIfDynamicOrFunction(node.function);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIndexExpression(IndexExpression node) {
|
||||
_lintIfDynamic(node.realTarget);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
var methodName = node.methodName.name;
|
||||
if (node.target != null) {
|
||||
if (methodName == 'noSuchMethod' &&
|
||||
node.argumentList.arguments.length == 1 &&
|
||||
node.argumentList.arguments.first is! NamedExpression) {
|
||||
// Special-cased; these exist on every object, even those typed "Object?".
|
||||
return;
|
||||
}
|
||||
if (methodName == 'toString' && node.argumentList.arguments.isEmpty) {
|
||||
// Special-cased; these exist on every object, even those typed "Object?".
|
||||
return;
|
||||
}
|
||||
}
|
||||
var receiverWasDynamic = _lintIfDynamic(node.realTarget);
|
||||
if (!receiverWasDynamic) {
|
||||
var target = node.target;
|
||||
// The ".call" method is special, where "a.call()" is treated ~as "a()".
|
||||
//
|
||||
// If the method is "call", and the receiver is a function, we assume then
|
||||
// we are really checking the static type of the receiver, not the static
|
||||
// type of the "call" method itself.
|
||||
DartType? staticType;
|
||||
if (methodName == 'call' &&
|
||||
target != null &&
|
||||
target.staticType is FunctionType) {
|
||||
staticType = target.staticType;
|
||||
}
|
||||
_lintIfDynamicOrFunction(node.function, staticType: staticType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPostfixExpression(PostfixExpression node) {
|
||||
if (node.operator.type == TokenType.BANG) {
|
||||
// x! is not a dynamic call, even if "x" is dynamic.
|
||||
return;
|
||||
}
|
||||
_lintPrefixOrPostfixExpression(node, node.operand);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPrefixedIdentifier(PrefixedIdentifier node) {
|
||||
var property = node.identifier.name;
|
||||
if (const {
|
||||
'hashCode',
|
||||
'noSuchMethod',
|
||||
'runtimeType',
|
||||
'toString',
|
||||
}.contains(property)) {
|
||||
// Special-cased; these exist on every object, even those typed "Object?".
|
||||
return;
|
||||
}
|
||||
_lintIfDynamic(node.prefix);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPrefixExpression(PrefixExpression node) {
|
||||
_lintPrefixOrPostfixExpression(node, node.operand);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPropertyAccess(PropertyAccess node) {
|
||||
_lintIfDynamic(node.realTarget);
|
||||
}
|
||||
|
||||
bool _lintIfDynamic(Expression? node) {
|
||||
if (node?.staticType is DynamicType) {
|
||||
rule.reportLint(node);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void _lintIfDynamicOrFunction(Expression node, {DartType? staticType}) {
|
||||
staticType ??= node.staticType;
|
||||
if (staticType == null) {
|
||||
return;
|
||||
}
|
||||
if (staticType is DynamicType) {
|
||||
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 is DynamicType) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc = r'Avoid empty else statements.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** empty else statements.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
if (x > y)
|
||||
print("1");
|
||||
else ;
|
||||
print("2");
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addIfStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
var elseStatement = node.elseStatement;
|
||||
if (elseStatement is EmptyStatement &&
|
||||
!elseStatement.semicolon.isSynthetic) {
|
||||
rule.reportLint(elseStatement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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.
|
||||
|
||||
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 '../ast.dart';
|
||||
|
||||
const _desc =
|
||||
r'Avoid overloading operator == and hashCode on classes not marked `@immutable`.';
|
||||
|
||||
const _details = r'''
|
||||
From [Effective Dart](https://dart.dev/effective-dart/design#avoid-defining-custom-equality-for-mutable-classes):
|
||||
|
||||
**AVOID** overloading operator == and hashCode on classes not marked `@immutable`.
|
||||
|
||||
If a class is not immutable, overloading `operator ==` and `hashCode` can
|
||||
lead to unpredictable and undesirable behavior when used in collections.
|
||||
|
||||
**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
|
||||
class A {
|
||||
final String key;
|
||||
const A(this.key);
|
||||
@override
|
||||
operator ==(other) => other is A && other.key == key;
|
||||
@override
|
||||
int get hashCode => key.hashCode;
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: The lint checks the use of the `@immutable` annotation, and will trigger
|
||||
even if the class is otherwise not mutable. Thus:
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class C {
|
||||
final String key;
|
||||
const C(this.key);
|
||||
@override
|
||||
operator ==(other) => other is C && other.key == key;
|
||||
@override
|
||||
int get hashCode => key.hashCode;
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
/// The name of the top-level variable used to mark a immutable class.
|
||||
String _immutableVarName = 'immutable';
|
||||
|
||||
/// The name of `meta` library, used to define analysis annotations.
|
||||
String _metaLibName = 'meta';
|
||||
|
||||
bool _isImmutable(Element? element) =>
|
||||
element is PropertyAccessorElement &&
|
||||
element.name == _immutableVarName &&
|
||||
element.library.name == _metaLibName;
|
||||
|
||||
class AvoidEqualsAndHashCodeOnMutableClasses extends LintRule {
|
||||
static const LintCode code = LintCode(
|
||||
'avoid_equals_and_hash_code_on_mutable_classes',
|
||||
"The method '{0}' should not be overridden in classes not annotated with '@immutable'.",
|
||||
correctionMessage:
|
||||
"Try removing the override or annotating the class with '@immutable'.");
|
||||
|
||||
AvoidEqualsAndHashCodeOnMutableClasses()
|
||||
: super(
|
||||
name: 'avoid_equals_and_hash_code_on_mutable_classes',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (node.name.type == TokenType.EQ_EQ || isHashCode(node)) {
|
||||
var classElement = _getClassForMethod(node);
|
||||
if (classElement != null && !_hasImmutableAnnotation(classElement)) {
|
||||
rule.reportLintForToken(node.firstTokenAfterCommentAndMetadata,
|
||||
arguments: [node.name.lexeme]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassElement? _getClassForMethod(MethodDeclaration node) =>
|
||||
// todo (pq): should this be ClassOrMixinDeclaration ?
|
||||
node.thisOrAncestorOfType<ClassDeclaration>()?.declaredElement;
|
||||
|
||||
bool _hasImmutableAnnotation(ClassElement clazz) {
|
||||
var inheritedAndSelfElements = <InterfaceElement>[
|
||||
...clazz.allSupertypes.map((t) => t.element),
|
||||
clazz,
|
||||
];
|
||||
var inheritedAndSelfAnnotations = inheritedAndSelfElements
|
||||
.expand((c) => c.metadata)
|
||||
.map((m) => m.element);
|
||||
return inheritedAndSelfAnnotations.any(_isImmutable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc = r'Avoid escaping inner quotes by converting surrounding quotes.';
|
||||
|
||||
const _details = r'''
|
||||
Avoid escaping inner quotes by converting surrounding quotes.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
var s = 'It\'s not fun';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
var s = "It's not fun";
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addSimpleStringLiteral(this, visitor);
|
||||
registry.addStringInterpolation(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitSimpleStringLiteral(SimpleStringLiteral node) {
|
||||
if (node.isRaw || node.isMultiline) return;
|
||||
_check(node, node.value, node.isSingleQuoted);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStringInterpolation(StringInterpolation node) {
|
||||
if (node.isRaw || node.isMultiline) return;
|
||||
|
||||
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}) =>
|
||||
text.contains(isSingleQuoted ? "'" : '"') &&
|
||||
!text.contains(isSingleQuoted ? '"' : "'");
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// 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.
|
||||
|
||||
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'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
|
||||
not allocate a useless field. As of April 2018 this is true for the VM, but not
|
||||
for code that will be compiled to JS.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class A {
|
||||
final a = const [];
|
||||
const A();
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class A {
|
||||
get a => const [];
|
||||
const A();
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
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.addConstructorFieldInitializer(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class HasParameterReferenceVisitor extends RecursiveAstVisitor {
|
||||
Iterable<ParameterElement?> parameters;
|
||||
|
||||
bool useParameter = false;
|
||||
|
||||
HasParameterReferenceVisitor(this.parameters);
|
||||
|
||||
@override
|
||||
void visitSimpleIdentifier(SimpleIdentifier node) {
|
||||
if (parameters.contains(node.staticElement)) {
|
||||
useParameter = true;
|
||||
} else {
|
||||
super.visitSimpleIdentifier(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
|
||||
var declaration = node.parent;
|
||||
if (declaration is ConstructorDeclaration) {
|
||||
if (declaration.constKeyword == null) return;
|
||||
var classDecl = declaration.thisOrAncestorOfType<ClassDeclaration>();
|
||||
if (classDecl == null) return;
|
||||
// no lint if several constructors
|
||||
var constructorCount =
|
||||
classDecl.members.whereType<ConstructorDeclaration>().length;
|
||||
if (constructorCount > 1) return;
|
||||
|
||||
var visitor = HasParameterReferenceVisitor(
|
||||
declaration.parameters.parameterElements);
|
||||
node.expression.accept(visitor);
|
||||
if (!visitor.useParameter) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldDeclaration(FieldDeclaration node) {
|
||||
if (node.isStatic) return;
|
||||
if (!node.fields.isFinal) return;
|
||||
// only const class
|
||||
var parent = node.parent;
|
||||
if (parent is ClassDeclaration) {
|
||||
var declaredElement = parent.declaredElement;
|
||||
if (declaredElement == null) {
|
||||
return;
|
||||
}
|
||||
if (declaredElement.constructors.every((e) => !e.isConst)) {
|
||||
return;
|
||||
}
|
||||
for (var variable in node.fields.variables) {
|
||||
if (variable.initializer != null) {
|
||||
rule.reportLint(variable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2017, 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/type.dart';
|
||||
|
||||
import '../analyzer.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) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
for (var person in people) {
|
||||
...
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
bool _hasMethodChaining(MethodInvocation node) {
|
||||
var exp = node.target;
|
||||
while (exp is PrefixedIdentifier ||
|
||||
exp is MethodInvocation ||
|
||||
exp is PropertyAccess) {
|
||||
if (exp is PrefixedIdentifier) {
|
||||
exp = exp.prefix;
|
||||
} else if (exp is MethodInvocation) {
|
||||
return true;
|
||||
} else if (exp is PropertyAccess) {
|
||||
exp = exp.target;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isInsideCascade(AstNode node) =>
|
||||
node.thisOrAncestorMatching((n) => n is Statement || n is CascadeExpression)
|
||||
is CascadeExpression;
|
||||
|
||||
bool _isIterable(DartType? type) =>
|
||||
type != null && type.implementsInterface('Iterable', 'dart.core');
|
||||
|
||||
class AvoidFunctionLiteralsInForeachCalls 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.");
|
||||
|
||||
AvoidFunctionLiteralsInForeachCalls()
|
||||
: super(
|
||||
name: 'avoid_function_literals_in_foreach_calls',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addMethodInvocation(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
var target = node.target;
|
||||
if (target != null &&
|
||||
node.methodName.token.value() == 'forEach' &&
|
||||
node.argumentList.arguments.isNotEmpty &&
|
||||
node.argumentList.arguments.first is FunctionExpression &&
|
||||
_isIterable(target.staticType) &&
|
||||
!node.containsNullAwareInvocationInChain() &&
|
||||
!_hasMethodChaining(node) &&
|
||||
!_isInsideCascade(node)) {
|
||||
rule.reportLint(node.function);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// 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.
|
||||
|
||||
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 `==`.
|
||||
|
||||
The `==` operator is contractually required to be an equivalence relation;
|
||||
that is, symmetrically for all objects `o1` and `o2`, `o1 == o2` and `o2 == o1`
|
||||
must either both be true, or both be false.
|
||||
|
||||
> _NOTE_: Dart does not have true _value types_, so instead we consider a class
|
||||
> that implements `==` as a _proxy_ for identifying value types.
|
||||
|
||||
When using `implements`, you do not inherit the method body of `==`, making it
|
||||
nearly impossible to follow the contract of `==`. Classes that override `==`
|
||||
typically are usable directly in tests _without_ creating mocks or fakes as
|
||||
well. For example, for a given class `Size`:
|
||||
|
||||
```dart
|
||||
class Size {
|
||||
final int inBytes;
|
||||
const Size(this.inBytes);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is Size && other.inBytes == inBytes;
|
||||
|
||||
@override
|
||||
int get hashCode => inBytes.hashCode;
|
||||
}
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class CustomSize implements Size {
|
||||
final int inBytes;
|
||||
const CustomSize(this.inBytes);
|
||||
|
||||
int get inKilobytes => inBytes ~/ 1000;
|
||||
}
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'package:test/test.dart';
|
||||
import 'size.dart';
|
||||
|
||||
class FakeSize implements Size {
|
||||
int inBytes = 0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('should not throw on a size >1Kb', () {
|
||||
expect(() => someFunction(FakeSize()..inBytes = 1001), returnsNormally);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class ExtendedSize extends Size {
|
||||
ExtendedSize(int inBytes) : super(inBytes);
|
||||
|
||||
int get inKilobytes => inBytes ~/ 1000;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**:
|
||||
```dart
|
||||
import 'package:test/test.dart';
|
||||
import 'size.dart';
|
||||
|
||||
void main() {
|
||||
test('should not throw on a size >1Kb', () {
|
||||
expect(() => someFunction(Size(1001)), returnsNormally);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addClassDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
var implementsClause = node.implementsClause;
|
||||
if (implementsClause == null) {
|
||||
return;
|
||||
}
|
||||
for (var interface in implementsClause.interfaces) {
|
||||
var interfaceType = interface.type;
|
||||
if (interfaceType is InterfaceType &&
|
||||
_overridesEquals(interfaceType.element)) {
|
||||
rule.reportLint(interface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _overridesEquals(InterfaceElement element) {
|
||||
var method = element.lookUpConcreteMethod('==', element.library);
|
||||
var enclosing = method?.enclosingElement;
|
||||
return enclosing is ClassElement && !enclosing.isDartCoreObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.
|
||||
|
||||
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 '../extensions.dart';
|
||||
|
||||
const _desc = r"Don't explicitly initialize variables to null.";
|
||||
|
||||
const _details = r'''
|
||||
From [Effective Dart](https://dart.dev/effective-dart/usage#dont-explicitly-initialize-variables-to-null):
|
||||
|
||||
**DON'T** explicitly initialize variables to `null`.
|
||||
|
||||
If a variable has a non-nullable type or is `final`,
|
||||
Dart reports a compile error if you try to use it
|
||||
before it has been definitely initialized.
|
||||
If the variable is nullable and not `const` or `final`,
|
||||
then it is implicitly initialized to `null` for you.
|
||||
There's no concept of "uninitialized memory" in Dart
|
||||
and no need to explicitly initialize a variable to `null` to be "safe".
|
||||
Adding `= null` is redundant and unneeded.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
Item? bestDeal(List<Item> cart) {
|
||||
Item? bestItem = null;
|
||||
|
||||
for (final item in cart) {
|
||||
if (bestItem == null || item.price < bestItem.price) {
|
||||
bestItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
return bestItem;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
Item? bestDeal(List<Item> cart) {
|
||||
Item? bestItem;
|
||||
|
||||
for (final item in cart) {
|
||||
if (bestItem == null || item.price < bestItem.price) {
|
||||
bestItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
return bestItem;
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addVariableDeclaration(this, visitor);
|
||||
registry.addDefaultFormalParameter(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
final LinterContext context;
|
||||
|
||||
final bool nnbdEnabled;
|
||||
_Visitor(this.rule, this.context)
|
||||
: nnbdEnabled = context.isEnabled(Feature.non_nullable);
|
||||
|
||||
bool isNullable(DartType type) =>
|
||||
!nnbdEnabled || context.typeSystem.isNullable(type);
|
||||
|
||||
@override
|
||||
void visitDefaultFormalParameter(DefaultFormalParameter node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
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 (node.defaultValue.isNullLiteral && isNullable(declaredElement.type)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitVariableDeclaration(VariableDeclaration node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement != null &&
|
||||
!node.isConst &&
|
||||
!node.isFinal &&
|
||||
node.initializer.isNullLiteral &&
|
||||
isNullable(declaredElement.type)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc = r'Avoid JavaScript rounded ints.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** integer literals that cannot be represented exactly when compiled to
|
||||
JavaScript.
|
||||
|
||||
When a program is compiled to JavaScript `int` and `double` become JavaScript
|
||||
Numbers. Too large integers (`value < Number.MIN_SAFE_INTEGER` or
|
||||
`value > Number.MAX_SAFE_INTEGER`) may be rounded to the closest Number value.
|
||||
|
||||
For instance `1000000000000000001` cannot be represented exactly as a JavaScript
|
||||
Number, so `1000000000000000000` will be used instead.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
int value = 9007199254740995;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
BigInt value = BigInt.parse('9007199254740995');
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addIntegerLiteral(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
bool isRounded(int? value) => value?.toDouble().toInt() != value;
|
||||
@override
|
||||
void visitIntegerLiteral(IntegerLiteral node) {
|
||||
if (isRounded(node.value)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 declare multiple variables on a single line.";
|
||||
|
||||
const _details = r'''
|
||||
**DON'T** declare multiple variables on a single line.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
String? foo, bar, baz;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
String? foo;
|
||||
String? bar;
|
||||
String? baz;
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addVariableDeclarationList(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitVariableDeclarationList(VariableDeclarationList node) {
|
||||
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.reportLintForToken(secondVariable.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2017, 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/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 '../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 value, no instance of any class (other than `Null`) can be
|
||||
equivalent to it. Thus, it is redundant to check whether the other instance is
|
||||
null.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class Person {
|
||||
final String? name;
|
||||
|
||||
@override
|
||||
operator ==(Object? other) =>
|
||||
other != null && other is Person && name == other.name;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class Person {
|
||||
final String? name;
|
||||
|
||||
@override
|
||||
operator ==(Object? other) => other is Person && name == other.name;
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
bool _isComparingEquality(TokenType tokenType) =>
|
||||
tokenType == TokenType.BANG_EQ || tokenType == TokenType.EQ_EQ;
|
||||
|
||||
bool _isComparingParameterWithNull(BinaryExpression node, Element? parameter) =>
|
||||
_isComparingEquality(node.operator.type) &&
|
||||
((node.leftOperand.isNullLiteral &&
|
||||
_isParameter(node.rightOperand, parameter)) ||
|
||||
(node.rightOperand.isNullLiteral &&
|
||||
_isParameter(node.leftOperand, parameter)));
|
||||
|
||||
bool _isParameter(Expression expression, Element? parameter) =>
|
||||
expression.canonicalElement == parameter;
|
||||
|
||||
bool _isParameterWithQuestionQuestion(
|
||||
BinaryExpression node, Element? parameter) =>
|
||||
node.operator.type == TokenType.QUESTION_QUESTION &&
|
||||
_isParameter(node.leftOperand, parameter);
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
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, {required this.nnbdEnabled});
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
var parameters = node.parameters?.parameters;
|
||||
if (parameters == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2017, 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:collection/collection.dart';
|
||||
|
||||
import '../analyzer.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
|
||||
ambiguous. Using named boolean parameters is much more readable because it
|
||||
inherently describes what the boolean value represents.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
Task(true);
|
||||
Task(false);
|
||||
ListBox(false, true, true);
|
||||
Button(false);
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
Task.oneShot();
|
||||
Task.repeating();
|
||||
ListBox(scroll: true, showScrollbars: true);
|
||||
Button(ButtonState.enabled);
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addConstructorDeclaration(this, visitor);
|
||||
registry.addFunctionDeclaration(this, visitor);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
registry.addGenericFunctionType(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
final LinterContext context;
|
||||
|
||||
_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) {
|
||||
checkParams(node.parameters.parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement != null && !declaredElement.isPrivate) {
|
||||
checkParams(node.functionExpression.parameters?.parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement != null &&
|
||||
!node.isSetter &&
|
||||
!declaredElement.isPrivate &&
|
||||
!node.isOperator &&
|
||||
!node.hasInheritedMethod &&
|
||||
!_isOverridingMember(declaredElement)) {
|
||||
checkParams(node.parameters?.parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitGenericFunctionType(GenericFunctionType node) {
|
||||
checkParams(node.parameters.parameters);
|
||||
}
|
||||
|
||||
bool _isOverridingMember(Element member) {
|
||||
var classElement = member.thisOrAncestorOfType<ClassElement>();
|
||||
if (classElement == null) return false;
|
||||
|
||||
var name = member.name;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.
|
||||
|
||||
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';
|
||||
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) {
|
||||
print('debug: $x');
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**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 {
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addMethodInvocation(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor {
|
||||
final LintRule rule;
|
||||
|
||||
_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.expression;
|
||||
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;
|
||||
if (element is FunctionElement &&
|
||||
element.name == 'print' &&
|
||||
element.library.isDartCore) {
|
||||
rule.reportLint(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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.
|
||||
|
||||
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'Avoid private typedef functions.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** private typedef functions used only once. Prefer inline function
|
||||
syntax.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
typedef void _F();
|
||||
m(_F f);
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
m(void Function() f);
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addFunctionTypeAlias(this, visitor);
|
||||
registry.addGenericTypeAlias(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _CountVisitor extends RecursiveAstVisitor {
|
||||
final String type;
|
||||
int count = 0;
|
||||
_CountVisitor(this.type);
|
||||
|
||||
@override
|
||||
void visitNamedType(NamedType node) {
|
||||
if (node.name2.lexeme == type) count++;
|
||||
super.visitNamedType(node);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
final LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
@override
|
||||
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
||||
_countAndReport(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitGenericTypeAlias(GenericTypeAlias node) {
|
||||
if (node.typeParameters != null) return;
|
||||
if (node.type is NamedType) return;
|
||||
if (node.type is RecordTypeAnnotation) return;
|
||||
|
||||
_countAndReport(node.name);
|
||||
}
|
||||
|
||||
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.reportLintForToken(identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// 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.
|
||||
|
||||
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'''
|
||||
**DON'T** pass an argument that matches the corresponding parameter's default
|
||||
value.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void f({bool valWithDefault = true, bool? val}) {
|
||||
...
|
||||
}
|
||||
|
||||
void main() {
|
||||
f(valWithDefault: true);
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void f({bool valWithDefault = true, bool? val}) {
|
||||
...
|
||||
}
|
||||
|
||||
void main() {
|
||||
f(valWithDefault: false);
|
||||
f();
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor {
|
||||
final LintRule rule;
|
||||
final LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
void check(ArgumentList argumentList) {
|
||||
var arguments = argumentList.arguments;
|
||||
if (arguments.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = arguments.length - 1; i >= 0; --i) {
|
||||
var arg = arguments[i];
|
||||
var param = arg.staticParameterElement;
|
||||
if (arg is NamedExpression) {
|
||||
arg = arg.expression;
|
||||
}
|
||||
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();
|
||||
// todo(pq): reenable and do ecosystem cleanup (https://github.com/dart-lang/linter/issues/4368)
|
||||
// if (value == null && arg is NullLiteral) {
|
||||
// rule.reportLint(arg);
|
||||
// } else ...
|
||||
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) {
|
||||
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 _redirected 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
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
check(node.argumentList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc = r'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.
|
||||
|
||||
You can also use 'always_use_package_imports' to disallow relative imports
|
||||
between files within `lib/`.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'package:foo/bar.dart';
|
||||
|
||||
import '../lib/baz.dart';
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'package:foo/bar.dart';
|
||||
|
||||
import 'baz.dart';
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@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);
|
||||
|
||||
bool isRelativeLibImport(ImportDirective node) {
|
||||
// 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) {
|
||||
return uri.path.contains('/lib/');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitImportDirective(ImportDirective node) {
|
||||
if (isRelativeLibImport(node)) {
|
||||
rule.reportLint(node.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
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"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 `dart doc` produces
|
||||
documentation. If the inherited method contains the name of the parameter (in
|
||||
square brackets), then `dart doc` cannot link it correctly.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
abstract class A {
|
||||
m(a);
|
||||
}
|
||||
|
||||
abstract class B extends A {
|
||||
m(b);
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
abstract class A {
|
||||
m(a);
|
||||
}
|
||||
|
||||
abstract class B extends A {
|
||||
m(a);
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
class AvoidRenamingMethodParameters extends LintRule {
|
||||
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}'.");
|
||||
|
||||
AvoidRenamingMethodParameters()
|
||||
: super(
|
||||
name: 'avoid_renaming_method_parameters',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => parameterCode;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
if (!isInLibDir(context.currentUnit.unit, context.package)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var visitor = _Visitor(this);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (node.isStatic) return;
|
||||
if (node.documentationComment != null) return;
|
||||
|
||||
var parentNode = node.parent;
|
||||
if (parentNode is! Declaration) {
|
||||
return;
|
||||
}
|
||||
var parentElement = parentNode.declaredElement;
|
||||
// Note: there are no override semantics with extension methods.
|
||||
if (parentElement is! InterfaceElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
var classElement = parentElement;
|
||||
|
||||
if (classElement.isPrivate) return;
|
||||
|
||||
var parentMethod = classElement.lookUpInheritedMethod(
|
||||
node.name.lexeme, classElement.library);
|
||||
|
||||
if (parentMethod == null) return;
|
||||
|
||||
var nodeParams = node.parameters;
|
||||
if (nodeParams == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = nodeParams.parameters.where((p) => !p.isNamed).toList();
|
||||
var parentParameters =
|
||||
parentMethod.parameters.where((p) => !p.isNamed).toList();
|
||||
var count = math.min(parameters.length, parentParameters.length);
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (parentParameters.length <= i) break;
|
||||
var paramIdentifier = parameters[i].name;
|
||||
if (paramIdentifier != null &&
|
||||
paramIdentifier.lexeme != parentParameters[i].name) {
|
||||
rule.reportLintForToken(paramIdentifier,
|
||||
arguments: [paramIdentifier.lexeme, parentParameters[i].name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void set speed(int ms);
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
set speed(int ms);
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
class AvoidReturnTypesOnSetters extends LintRule {
|
||||
@override
|
||||
bool get canUseParsedResult => true;
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFunctionDeclaration(this, visitor);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (node.isSetter) {
|
||||
if (node.returnType != null) {
|
||||
rule.reportLint(node.returnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (node.isSetter) {
|
||||
if (node.returnType != null) {
|
||||
rule.reportLint(node.returnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2017, 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/type.dart';
|
||||
|
||||
import '../analyzer.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.
|
||||
|
||||
Functions that return primitive types such as bool, double, int, and num are
|
||||
generally expected to return non-nullable values. Thus, returning null where a
|
||||
primitive type was expected can lead to runtime exceptions.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
bool getBool() => null;
|
||||
num getNum() => null;
|
||||
int getInt() => null;
|
||||
double getDouble() => null;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
bool getBool() => false;
|
||||
num getNum() => -1;
|
||||
int getInt() => -1;
|
||||
double getDouble() => -1.0;
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
bool _isPrimitiveType(DartType type) =>
|
||||
type is InterfaceType &&
|
||||
(type.isDartCoreBool ||
|
||||
type.isDartCoreDouble ||
|
||||
type.isDartCoreInt ||
|
||||
type.isDartCoreNum);
|
||||
|
||||
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'.");
|
||||
|
||||
AvoidReturningNull()
|
||||
: super(
|
||||
name: 'avoid_returning_null',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
state: State.deprecated(since: dart2_12),
|
||||
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.
|
||||
// Long-term it should be deprecated and slated for removal.
|
||||
// See: https://github.com/dart-lang/linter/issues/2636
|
||||
if (!context.isEnabled(Feature.non_nullable)) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFunctionExpression(this, visitor);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFunctionExpression(FunctionExpression node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement != null &&
|
||||
_isPrimitiveType(declaredElement.returnType)) {
|
||||
_visitFunctionBody(node.body);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement != null &&
|
||||
_isPrimitiveType(declaredElement.returnType)) {
|
||||
_visitFunctionBody(node.body);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitFunctionBody(FunctionBody node) {
|
||||
if (node is ExpressionFunctionBody && node.expression.isNullLiteral) {
|
||||
rule.reportLint(node);
|
||||
return;
|
||||
}
|
||||
|
||||
node.accept(_BodyVisitor(rule));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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.
|
||||
|
||||
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/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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
|
||||
developer simply forgot to put an `async` keyword on the function.
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
state: State.deprecated(since: dart2_12),
|
||||
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
|
||||
// analysis.
|
||||
if (!context.isEnabled(Feature.non_nullable)) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addExpressionFunctionBody(this, visitor);
|
||||
registry.addReturnStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
||||
_visit(node, node.expression);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitReturnStatement(ReturnStatement node) {
|
||||
var expression = node.expression;
|
||||
if (expression != null) {
|
||||
_visit(node, expression);
|
||||
}
|
||||
}
|
||||
|
||||
void _visit(AstNode node, Expression expression) {
|
||||
if (expression.staticType?.isDartCoreNull != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = node.thisOrAncestorMatching(
|
||||
(e) => e is FunctionExpression || e is MethodDeclaration);
|
||||
if (parent == null) return;
|
||||
|
||||
DartType? returnType;
|
||||
bool isAsync;
|
||||
if (parent is FunctionExpression) {
|
||||
returnType = parent.declaredElement?.returnType;
|
||||
isAsync = parent.body.isAsynchronous;
|
||||
} else if (parent is MethodDeclaration) {
|
||||
returnType = parent.declaredElement?.returnType;
|
||||
isAsync = parent.body.isAsynchronous;
|
||||
} else {
|
||||
throw StateError('unexpected type');
|
||||
}
|
||||
if (returnType != null && returnType.isDartAsyncFuture && !isAsync) {
|
||||
rule.reportLint(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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
|
||||
a function doesn't return anything. Dart allows returning `null` in functions
|
||||
with `void` return type but it also allow using `return;` without specifying any
|
||||
value. To have a consistent way you should not return `null` and only use an
|
||||
empty return.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void f1() {
|
||||
return null;
|
||||
}
|
||||
Future<void> f2() async {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void f1() {
|
||||
return;
|
||||
}
|
||||
Future<void> f2() async {
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
List<LintCode> get lintCodes => [fromFunction, fromMethod];
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addExpressionFunctionBody(this, visitor);
|
||||
registry.addReturnStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
||||
_visit(node, node.expression);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitReturnStatement(ReturnStatement node) {
|
||||
if (node.expression != null) {
|
||||
_visit(node, node.expression);
|
||||
}
|
||||
}
|
||||
|
||||
void _visit(AstNode node, Expression? expression) {
|
||||
if (expression is! NullLiteral) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = node.thisOrAncestorMatching(
|
||||
(e) => e is FunctionExpression || e is MethodDeclaration);
|
||||
if (parent == null) return;
|
||||
|
||||
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 is VoidType) {
|
||||
rule.reportLint(node, errorCode: code);
|
||||
} else if (isAsync &&
|
||||
type.isDartAsyncFuture &&
|
||||
(type as InterfaceType).typeArguments.first is VoidType) {
|
||||
rule.reportLint(node, errorCode: code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2017, 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/type.dart';
|
||||
|
||||
import '../analyzer.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
|
||||
allows method chaining universally.
|
||||
|
||||
Returning `this` is allowed for:
|
||||
|
||||
- operators
|
||||
- methods with a return type different of the current class
|
||||
- methods defined in parent classes / mixins or interfaces
|
||||
- methods defined in extensions
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
var buffer = StringBuffer()
|
||||
.write('one')
|
||||
.write('two')
|
||||
.write('three');
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
var buffer = StringBuffer()
|
||||
..write('one')
|
||||
..write('two')
|
||||
..write('three');
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
bool _returnsThis(ReturnStatement node) => node.expression is ThisExpression;
|
||||
|
||||
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.");
|
||||
|
||||
AvoidReturningThis()
|
||||
: super(
|
||||
name: 'avoid_returning_this',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _BodyVisitor extends RecursiveAstVisitor {
|
||||
List<ReturnStatement> returnStatements = [];
|
||||
|
||||
bool foundNonThisReturn = false;
|
||||
|
||||
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 we've encountered a non-this return.
|
||||
if (foundNonThisReturn) return;
|
||||
// Short-circuit if not returning this.
|
||||
if (!_returnsThis(node)) {
|
||||
foundNonThisReturn = true;
|
||||
returnStatements.clear();
|
||||
return;
|
||||
}
|
||||
returnStatements.add(node);
|
||||
super.visitReturnStatement(node);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (node.isOperator) return;
|
||||
|
||||
var parent = node.parent;
|
||||
if (parent is ClassDeclaration ||
|
||||
parent is EnumDeclaration ||
|
||||
parent is MixinDeclaration) {
|
||||
if (node.isOverride) {
|
||||
return;
|
||||
}
|
||||
|
||||
var returnType = node.declaredElement?.returnType;
|
||||
if (returnType is InterfaceType &&
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
returnType.element == (parent as Declaration).declaredElement) {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ignore Extensions.
|
||||
return;
|
||||
}
|
||||
|
||||
var body = node.body;
|
||||
if (body is BlockFunctionBody) {
|
||||
var returnStatements = _BodyVisitor().collectReturns(body);
|
||||
if (returnStatements.isNotEmpty) {
|
||||
rule.reportLint(returnStatements.first.expression);
|
||||
}
|
||||
} else if (body is ExpressionFunctionBody) {
|
||||
if (body.expression is ThisExpression) {
|
||||
rule.reportLintForToken(node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2017, 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'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
|
||||
inconsistencies. Doing this could allow you to set a property to some value,
|
||||
but then upon observing the property's value, it could easily be different.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class Bad {
|
||||
int l, r;
|
||||
|
||||
set length(int newLength) {
|
||||
r = l + newLength;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class Good {
|
||||
int l, r;
|
||||
|
||||
int get length => r - l;
|
||||
|
||||
set length(int newLength) {
|
||||
r = l + newLength;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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.');
|
||||
|
||||
AvoidSettersWithoutGetters()
|
||||
: super(
|
||||
name: 'avoid_setters_without_getters',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
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 &&
|
||||
member.lookUpInheritedConcreteSetter() == null &&
|
||||
member.lookUpGetter() == null) {
|
||||
rule.reportLintForToken(member.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc = r'Avoid shadowing type parameters.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** shadowing type parameters.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class A<T> {
|
||||
void fn<T>() {}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class A<T> {
|
||||
void fn<U>() {}
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFunctionDeclarationStatement(this, visitor);
|
||||
registry.addGenericTypeAlias(this, visitor);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
|
||||
var functionExpression = node.functionDeclaration.functionExpression;
|
||||
if (functionExpression.typeParameters != null) {
|
||||
_checkAncestorParameters(functionExpression.typeParameters, node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitGenericTypeAlias(GenericTypeAlias node) {
|
||||
var typeParameters = node.functionType?.typeParameters;
|
||||
if (typeParameters != null) {
|
||||
_checkForShadowing(typeParameters, node.typeParameters, 'typedef');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
if (node.typeParameters == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Static methods have nothing above them to shadow.
|
||||
if (!node.isStatic) {
|
||||
_checkAncestorParameters(node.typeParameters, node);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the ancestors of [node] for type parameter shadowing.
|
||||
void _checkAncestorParameters(
|
||||
TypeParameterList? typeParameters, AstNode node) {
|
||||
var parent = node.parent;
|
||||
|
||||
while (parent != null) {
|
||||
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, 'extension');
|
||||
} else if (parent is ExtensionTypeDeclaration) {
|
||||
_checkForShadowing(
|
||||
typeParameters, parent.typeParameters, 'extension type');
|
||||
} else if (parent is MethodDeclaration) {
|
||||
_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, 'function');
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether any of [typeParameters] shadow [ancestorTypeParameters].
|
||||
void _checkForShadowing(TypeParameterList? typeParameters,
|
||||
TypeParameterList? ancestorTypeParameters, String ancestorKind) {
|
||||
if (typeParameters == null || ancestorTypeParameters == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ancestorTypeParameterNames = ancestorTypeParameters.typeParameters
|
||||
.map((tp) => tp.name.lexeme)
|
||||
.toSet();
|
||||
|
||||
for (var parameter in typeParameters.typeParameters) {
|
||||
if (ancestorTypeParameterNames.contains(parameter.name.lexeme)) {
|
||||
rule.reportLint(parameter,
|
||||
arguments: [parameter.name.lexeme, ancestorKind]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
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'Avoid single cascade in expression statements.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** single cascade in expression statements.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
o..m();
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
o.m();
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addCascadeExpression(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_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) {
|
||||
var sections = node.cascadeSections;
|
||||
if (sections.length == 1 && node.parent is ExpressionStatement) {
|
||||
var operator = operatorFor(sections[0]);
|
||||
rule.reportLint(node, arguments: [operator]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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.
|
||||
|
||||
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 '../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.
|
||||
|
||||
* `Directory.exists`
|
||||
* `Directory.stat`
|
||||
* `File.lastModified`
|
||||
* `File.exists`
|
||||
* `File.stat`
|
||||
* `FileSystemEntity.isDirectory`
|
||||
* `FileSystemEntity.isFile`
|
||||
* `FileSystemEntity.isLink`
|
||||
* `FileSystemEntity.type`
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'dart:io';
|
||||
|
||||
Future<Null> someFunction() async {
|
||||
var file = File('/path/to/my/file');
|
||||
var now = DateTime.now();
|
||||
if ((await file.lastModified()).isBefore(now)) print('before'); // LINT
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'dart:io';
|
||||
|
||||
Future<Null> someFunction() async {
|
||||
var file = File('/path/to/my/file');
|
||||
var now = DateTime.now();
|
||||
if (file.lastModifiedSync().isBefore(now)) print('before'); // OK
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
const List<String> _dirMethodNames = <String>[
|
||||
'exists',
|
||||
'stat',
|
||||
];
|
||||
|
||||
const List<String> _fileMethodNames = <String>[
|
||||
'lastModified',
|
||||
'exists',
|
||||
'stat',
|
||||
];
|
||||
|
||||
const List<String> _fileSystemEntityMethodNames = <String>[
|
||||
'isDirectory',
|
||||
'isFile',
|
||||
'isLink',
|
||||
'type',
|
||||
];
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addMethodInvocation(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
if (node.argumentList.arguments.isEmpty) {
|
||||
var type = node.target?.staticType;
|
||||
_checkFileMethods(node, type);
|
||||
_checkDirectoryMethods(node, type);
|
||||
return;
|
||||
} else {
|
||||
_checkFileSystemEntityMethods(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _checkDirectoryMethods(MethodInvocation node, DartType? type) {
|
||||
if (type.extendsClass('Directory', 'dart.io')) {
|
||||
if (_dirMethodNames.contains(node.methodName.name)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkFileMethods(MethodInvocation node, DartType? type) {
|
||||
if (type.extendsClass('File', 'dart.io')) {
|
||||
if (_fileMethodNames.contains(node.methodName.name)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkFileSystemEntityMethods(MethodInvocation node) {
|
||||
var target = node.target;
|
||||
if (target is Identifier) {
|
||||
var elem = target.staticElement;
|
||||
if (elem is ClassElement && elem.name == 'FileSystemEntity') {
|
||||
if (_fileSystemEntityMethodNames.contains(node.methodName.name)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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 '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 <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,
|
||||
but release-mode compilers often choose to minify these symbols.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void bar(Object other) {
|
||||
if (other.runtimeType.toString() == 'Bar') {
|
||||
doThing();
|
||||
}
|
||||
}
|
||||
|
||||
Object baz(Thing myThing) {
|
||||
return getThingFromDatabase(key: myThing.runtimeType.toString());
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void bar(Object other) {
|
||||
if (other is Bar) {
|
||||
doThing();
|
||||
}
|
||||
}
|
||||
|
||||
class Thing {
|
||||
String get thingTypeKey => ...
|
||||
}
|
||||
|
||||
Object baz(Thing myThing) {
|
||||
return getThingFromDatabase(key: myThing.thingTypeKey);
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
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.typeType);
|
||||
// Gathering meta information at these nodes.
|
||||
// Nodes visited in DFS, so this will be called before
|
||||
// each visitMethodInvocation.
|
||||
registry.addClassDeclaration(this, visitor);
|
||||
registry.addMixinDeclaration(this, visitor);
|
||||
registry.addExtensionDeclaration(this, visitor);
|
||||
|
||||
registry.addArgumentList(this, visitor);
|
||||
|
||||
// Actually checking things at these nodes.
|
||||
// Also delegates to visitArgumentList.
|
||||
registry.addMethodInvocation(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor {
|
||||
final LintRule rule;
|
||||
final TypeSystem typeSystem;
|
||||
final InterfaceType typeType;
|
||||
|
||||
// Null if there is no logical `this` in the given context.
|
||||
InterfaceType? thisType;
|
||||
|
||||
_Visitor(this.rule, this.typeSystem, this.typeType);
|
||||
|
||||
@override
|
||||
void visitArgumentList(ArgumentList node) {
|
||||
node.arguments.forEach(_validateArgument);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
thisType = node.declaredElement?.thisType;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
||||
var extendedType = node.declaredElement?.extendedType;
|
||||
// Might not be InterfaceType. Ex: visiting an extension on a dynamic type.
|
||||
thisType = extendedType is InterfaceType ? extendedType : null;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
visitArgumentList(node.argumentList);
|
||||
|
||||
var staticType = node.realTarget?.staticType;
|
||||
var targetType = staticType is InterfaceType ? staticType : thisType;
|
||||
_reportIfToStringOnCoreTypeClass(targetType, node.methodName);
|
||||
}
|
||||
|
||||
@override
|
||||
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) {
|
||||
if (expression is PropertyAccess) {
|
||||
var expressionType = expression.realTarget.staticType;
|
||||
var targetType =
|
||||
(expressionType is InterfaceType) ? expressionType : thisType;
|
||||
_reportIfToStringOnCoreTypeClass(targetType, expression.propertyName);
|
||||
} else if (expression is PrefixedIdentifier) {
|
||||
var prefixType = expression.prefix.staticType;
|
||||
if (prefixType is InterfaceType) {
|
||||
_reportIfToStringOnCoreTypeClass(prefixType, expression.identifier);
|
||||
}
|
||||
} else if (expression is SimpleIdentifier) {
|
||||
_reportIfToStringOnCoreTypeClass(thisType, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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.
|
||||
|
||||
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';
|
||||
|
||||
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:**
|
||||
```dart
|
||||
m(f(int));
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
m(f(int v));
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addFormalParameterList(this, visitor);
|
||||
registry.addCatchClause(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
final LinterContext context;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
@override
|
||||
void visitCatchClause(CatchClause node) {
|
||||
var parameter = node.exceptionParameter;
|
||||
if (parameter != null && _isTypeName(node, parameter.name)) {
|
||||
rule.reportLint(parameter, arguments: [parameter.name.lexeme]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFormalParameterList(FormalParameterList node) {
|
||||
for (var parameter in node.parameters) {
|
||||
var declaredElement = parameter.declaredElement;
|
||||
var name = parameter.name;
|
||||
if (declaredElement != null &&
|
||||
declaredElement is! FieldFormalParameterElement &&
|
||||
declaredElement.hasImplicitType &&
|
||||
name != null &&
|
||||
_isTypeName(node, name)) {
|
||||
rule.reportLintForToken(name, arguments: [name.lexeme]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ||
|
||||
element is TypeParameterElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2017, 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/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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
|
||||
because the parameter types can almost always be inferred from the context,
|
||||
thus making the practice redundant.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
var names = people.map((Person person) => person.name);
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
var names = people.map((person) => person.name);
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
List<String> get incompatibleRules => const ['always_specify_types'];
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFunctionExpression(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class AvoidTypesOnClosureParametersVisitor extends SimpleAstVisitor {
|
||||
LintRule rule;
|
||||
|
||||
AvoidTypesOnClosureParametersVisitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitDefaultFormalParameter(DefaultFormalParameter node) {
|
||||
node.parameter.accept(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionExpression(FunctionExpression node) {
|
||||
if (node.parent is FunctionDeclaration) {
|
||||
return;
|
||||
}
|
||||
var parameterList = node.parameters?.parameters;
|
||||
if (parameterList != null) {
|
||||
for (var parameter in parameterList) {
|
||||
parameter.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleFormalParameter(SimpleFormalParameter node) {
|
||||
var type = node.type;
|
||||
if (type is NamedType && type.type is! DynamicType) {
|
||||
rule.reportLint(node.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFunctionExpression(FunctionExpression node) {
|
||||
var visitor = AvoidTypesOnClosureParametersVisitor(rule);
|
||||
visitor.visitFunctionExpression(node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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.
|
||||
|
||||
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'Avoid 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.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
Widget buildRow() {
|
||||
return Container(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const MyLogo(),
|
||||
const Expanded(
|
||||
child: Text('...'),
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
Widget buildRow() {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
const MyLogo(),
|
||||
const Expanded(
|
||||
child: Text('...'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@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 (!isWidgetType(node.staticType)) {
|
||||
return;
|
||||
}
|
||||
var parent = node.parent;
|
||||
if (parent is NamedExpression && parent.name.label.name == 'child') {
|
||||
var args = parent.thisOrAncestorOfType<ArgumentList>();
|
||||
if (args?.arguments.length == 1) {
|
||||
var parentCreation =
|
||||
parent.thisOrAncestorOfType<InstanceCreationExpression>();
|
||||
if (parentCreation != null) {
|
||||
if (isExactWidgetTypeContainer(parentCreation.staticType)) {
|
||||
rule.reportLint(parentCreation.constructorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2017, 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';
|
||||
import '../util/ascii_utils.dart';
|
||||
|
||||
const _desc = r'Avoid defining unused parameters in constructors.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** defining unused parameters in constructors.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class BadOne {
|
||||
BadOne(int unusedParameter, [String unusedPositional]);
|
||||
}
|
||||
|
||||
class BadTwo {
|
||||
int c;
|
||||
|
||||
BadTwo(int a, int b, int x) {
|
||||
c = a + b;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class _ConstructorVisitor extends RecursiveAstVisitor {
|
||||
final ConstructorDeclaration element;
|
||||
final Set<FormalParameter> unusedParameters;
|
||||
|
||||
_ConstructorVisitor(this.element)
|
||||
: unusedParameters = element.parameters.parameters.where((p) {
|
||||
var element = p.declaredElement;
|
||||
return element != null &&
|
||||
element is! FieldFormalParameterElement &&
|
||||
element is! SuperFormalParameterElement &&
|
||||
!element.hasDeprecated &&
|
||||
!element.name.isJustUnderscores;
|
||||
}).toSet();
|
||||
|
||||
@override
|
||||
void visitSimpleIdentifier(SimpleIdentifier node) {
|
||||
unusedParameters
|
||||
.removeWhere((p) => node.staticElement == p.declaredElement);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||
if (node.redirectedConstructor != null) return;
|
||||
if (node.externalKeyword != null) return;
|
||||
|
||||
var constructorVisitor = _ConstructorVisitor(node);
|
||||
node.body.visitChildren(constructorVisitor);
|
||||
for (var i in node.initializers) {
|
||||
i.visitChildren(constructorVisitor);
|
||||
}
|
||||
|
||||
for (var parameter in constructorVisitor.unusedParameters) {
|
||||
rule.reportLint(parameter, arguments: [parameter.name!.lexeme]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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.
|
||||
|
||||
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 async functions that return void.';
|
||||
|
||||
const _details = r'''
|
||||
**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`.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void f() async {}
|
||||
void f2() async => null;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
Future<void> f() async {}
|
||||
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 {
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFunctionDeclaration(this, visitor);
|
||||
registry.addMethodDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
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) {
|
||||
var arguments = _arguments(node.declaredElement);
|
||||
if (arguments != null && _isVoid(node.returnType)) {
|
||||
rule.reportLintForToken(node.name, arguments: arguments);
|
||||
}
|
||||
}
|
||||
|
||||
List<String>? _arguments(ExecutableElement? element) {
|
||||
if (element == null) {
|
||||
return null;
|
||||
} else if (element.isAsynchronous) {
|
||||
return ['Future', 'async'];
|
||||
} else if (element.isGenerator) {
|
||||
return ['Stream', 'async*'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isVoid(TypeAnnotation? typeAnnotation) =>
|
||||
typeAnnotation?.type is VoidType;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/file_system/file_system.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../ast.dart';
|
||||
|
||||
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
|
||||
`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
|
||||
Flutter web.
|
||||
|
||||
Web library access *is* allowed in:
|
||||
|
||||
* plugin packages that declare `web` as a supported context
|
||||
|
||||
otherwise, imports of `dart:html`, `dart:js` and `dart:js_util` are disallowed.
|
||||
''';
|
||||
|
||||
/// todo (pq): consider making a utility and sharing w/ `prefer_relative_imports`
|
||||
YamlMap _parseYaml(String content) {
|
||||
try {
|
||||
var doc = loadYamlNode(content);
|
||||
if (doc is YamlMap) {
|
||||
return doc;
|
||||
}
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (_) {
|
||||
// Fall-through.
|
||||
}
|
||||
return YamlMap();
|
||||
}
|
||||
|
||||
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 = {};
|
||||
|
||||
AvoidWebLibrariesInFlutter()
|
||||
: super(
|
||||
name: 'avoid_web_libraries_in_flutter',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
bool hasFlutterDep(File? pubspec) {
|
||||
if (pubspec == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
YamlMap parsedPubspec;
|
||||
try {
|
||||
var content = pubspec.readAsStringSync();
|
||||
parsedPubspec = _parseYaml(content);
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it has Flutter as a dependency, continue checking.
|
||||
if (parsedPubspec['dependencies'] case {'flutter': var _?}) {
|
||||
if (parsedPubspec['flutter']
|
||||
case {'plugin': {'platforms': {'web': _?}}}) {
|
||||
// Is a Flutter web plugin, allow web libraries.
|
||||
return false;
|
||||
} else {
|
||||
// Is a non-web Flutter package, don't allow web libraries.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Is not a Flutter package, allow web libraries.
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
bool hasFlutter(String root) {
|
||||
var hasFlutter = _rootHasFlutterCache[root];
|
||||
if (hasFlutter == null) {
|
||||
// clear previous cache
|
||||
_rootHasFlutterCache.clear();
|
||||
var pubspecFile = locatePubspecFile(context.currentUnit.unit);
|
||||
hasFlutter = hasFlutterDep(pubspecFile);
|
||||
_rootHasFlutterCache[root] = hasFlutter;
|
||||
}
|
||||
return hasFlutter;
|
||||
}
|
||||
|
||||
var root = context.package?.root;
|
||||
if (root != null) {
|
||||
if (hasFlutter(root)) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addImportDirective(this, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
bool isWebUri(String uri) {
|
||||
var uriLength = uri.length;
|
||||
return (uriLength == 9 && uri == 'dart:html') ||
|
||||
(uriLength == 7 && uri == 'dart:js') ||
|
||||
(uriLength == 12 && uri == 'dart:js_util');
|
||||
}
|
||||
|
||||
@override
|
||||
void visitImportDirective(ImportDirective node) {
|
||||
var uriString = node.uri.stringValue;
|
||||
if (uriString != null && isWebUri(uriString)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../extensions.dart';
|
||||
|
||||
const _desc = r'Await only futures.';
|
||||
|
||||
const _details = r'''
|
||||
**AVOID** using await on anything which is not a future.
|
||||
|
||||
Await is allowed on the types: `Future<X>`, `FutureOr<X>`, `Future<X>?`,
|
||||
`FutureOr<X>?` and `dynamic`.
|
||||
|
||||
Further, using `await null` is specifically allowed as a way to introduce a
|
||||
microtask delay.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
main() async {
|
||||
print(await 23);
|
||||
}
|
||||
```
|
||||
**GOOD:**
|
||||
```dart
|
||||
main() async {
|
||||
await null; // If a delay is really intended.
|
||||
print(23);
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addAwaitExpression(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitAwaitExpression(AwaitExpression node) {
|
||||
if (node.expression is NullLiteral) return;
|
||||
|
||||
var type = node.expression.staticType;
|
||||
if (!(type == null ||
|
||||
type.isDartAsyncFuture ||
|
||||
type is DynamicType ||
|
||||
type is InvalidType ||
|
||||
type.extendsClass('Future', 'dart.async') ||
|
||||
type.implementsInterface('Future', 'dart.async') ||
|
||||
type.isDartAsyncFutureOr)) {
|
||||
rule.reportLintForToken(node.awaitKeyword, arguments: [type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
const _desc = r'Name extensions using UpperCamelCase.';
|
||||
|
||||
const _details = r'''
|
||||
From [Effective Dart](https://dart.dev/effective-dart/style#do-name-extensions-using-uppercamelcase):
|
||||
|
||||
**DO** name extensions using `UpperCamelCase`.
|
||||
|
||||
Extensions should capitalize the first letter of each word (including
|
||||
the first word), and use no separators.
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
extension MyFancyList<T> on List<T> {
|
||||
// ...
|
||||
}
|
||||
|
||||
extension SmartIterable<T> on Iterable<T> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addExtensionDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
||||
var name = node.name;
|
||||
if (name != null && !isCamelCase(name.lexeme)) {
|
||||
rule.reportLintForToken(name, arguments: [name.lexeme]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
const _desc = r'Name types using UpperCamelCase.';
|
||||
|
||||
const _details = r'''
|
||||
From [Effective Dart](https://dart.dev/effective-dart/style#do-name-types-using-uppercamelcase):
|
||||
|
||||
**DO** name types using UpperCamelCase.
|
||||
|
||||
Classes and typedefs should capitalize the first letter of each word (including
|
||||
the first word), and use no separators.
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class SliderMenu {
|
||||
// ...
|
||||
}
|
||||
|
||||
class HttpRequest {
|
||||
// ...
|
||||
}
|
||||
|
||||
typedef num Adder(num x, num y);
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addGenericTypeAlias(this, visitor);
|
||||
registry.addClassDeclaration(this, visitor);
|
||||
registry.addClassTypeAlias(this, visitor);
|
||||
registry.addFunctionTypeAlias(this, visitor);
|
||||
registry.addEnumDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
void check(Token name) {
|
||||
var lexeme = name.lexeme;
|
||||
if (!isCamelCase(lexeme)) {
|
||||
rule.reportLintForToken(name, arguments: [lexeme]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
check(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassTypeAlias(ClassTypeAlias node) {
|
||||
check(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitEnumDeclaration(EnumDeclaration node) {
|
||||
check(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
||||
check(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitGenericTypeAlias(GenericTypeAlias node) {
|
||||
check(node.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
|
||||
import '../analyzer.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
|
||||
behavior.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class A {
|
||||
StreamSubscription _subscriptionA; // LINT
|
||||
void init(Stream stream) {
|
||||
_subscriptionA = stream.listen((_) {});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void someFunction() {
|
||||
StreamSubscription _subscriptionF; // LINT
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class B {
|
||||
StreamSubscription _subscriptionB; // OK
|
||||
void init(Stream stream) {
|
||||
_subscriptionB = stream.listen((_) {});
|
||||
}
|
||||
|
||||
void dispose(filename) {
|
||||
_subscriptionB.cancel();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void someFunctionOK() {
|
||||
StreamSubscription _subscriptionB; // OK
|
||||
_subscriptionB.cancel();
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
bool _isSubscription(DartType type) =>
|
||||
type.implementsInterface('StreamSubscription', 'dart.async');
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFieldDeclaration(this, visitor);
|
||||
registry.addVariableDeclarationStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends LeakDetectorProcessors {
|
||||
static const _cancelMethodName = 'cancel';
|
||||
|
||||
@override
|
||||
Map<DartTypePredicate, String> predicates = {
|
||||
_isSubscription: _cancelMethodName
|
||||
};
|
||||
|
||||
_Visitor(super.rule);
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
// 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.
|
||||
|
||||
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 '../extensions.dart';
|
||||
|
||||
const _desc = r'Cascade consecutive method invocations on the same reference.';
|
||||
|
||||
const _details = r'''
|
||||
**DO** Use the cascading style when successively invoking methods on the same
|
||||
reference.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
SomeClass someReference = SomeClass();
|
||||
someReference.firstMethod();
|
||||
someReference.secondMethod();
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
SomeClass someReference = SomeClass();
|
||||
...
|
||||
someReference.firstMethod();
|
||||
someReference.aProperty = value;
|
||||
someReference.secondMethod();
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
SomeClass someReference = SomeClass()
|
||||
..firstMethod()
|
||||
..aProperty = value
|
||||
..secondMethod();
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
SomeClass someReference = SomeClass();
|
||||
...
|
||||
someReference
|
||||
..firstMethod()
|
||||
..aProperty = value
|
||||
..secondMethod();
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
Element? _getElementFromVariableDeclarationStatement(
|
||||
VariableDeclarationStatement statement) {
|
||||
var variables = statement.variables.variables;
|
||||
if (variables.length == 1) {
|
||||
var variable = variables.single;
|
||||
if (variable.initializer is AwaitExpression) {
|
||||
// `await` has a higher precedence than a cascade, but because the token
|
||||
// `await` is followed by whitespace, it may look like the cascade binds
|
||||
// tighter, for example in `await Future.value([1,2,3])..forEach(print)`.
|
||||
//
|
||||
// In such a case, we should not return any cascadable element here.
|
||||
return null;
|
||||
}
|
||||
return variable.declaredElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ExecutableElement? _getExecutableElementFromMethodInvocation(
|
||||
MethodInvocation node) {
|
||||
if (_isInvokedWithoutNullAwareOperator(node.operator)) {
|
||||
var executableElement = node.methodName.canonicalElement;
|
||||
if (executableElement is ExecutableElement) {
|
||||
return executableElement;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Element? _getPrefixElementFromExpression(Expression rawExpression) {
|
||||
var expression = rawExpression.unParenthesized;
|
||||
if (expression is PrefixedIdentifier) {
|
||||
return expression.prefix.canonicalElement;
|
||||
} else if (expression is PropertyAccess &&
|
||||
_isInvokedWithoutNullAwareOperator(expression.operator) &&
|
||||
expression.target is SimpleIdentifier) {
|
||||
return expression.target.canonicalElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Element? _getTargetElementFromCascadeExpression(CascadeExpression node) =>
|
||||
node.target.canonicalElement;
|
||||
|
||||
Element? _getTargetElementFromMethodInvocation(MethodInvocation node) =>
|
||||
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 {
|
||||
static const LintCode code = LintCode(
|
||||
'cascade_invocations', 'Unnecessary duplication of receiver.',
|
||||
correctionMessage: 'Try using a cascade to avoid the duplication.');
|
||||
|
||||
/// Default constructor.
|
||||
CascadeInvocations()
|
||||
: super(
|
||||
name: 'cascade_invocations',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addBlock(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// A CascadableExpression is an object that is built from an expression and
|
||||
/// knows if it is able to join to another CascadableExpression.
|
||||
class _CascadableExpression {
|
||||
static final nullCascadableExpression =
|
||||
_CascadableExpression._internal(null, []);
|
||||
|
||||
/// 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;
|
||||
|
||||
/// This is necessary when you have a variable declaration so that element
|
||||
/// is critical and it can't be used as a parameter of a method invocation or
|
||||
/// in the right part of an assignment in a following expression that we would
|
||||
/// like to join to this.
|
||||
final bool isCritical;
|
||||
final Element? element;
|
||||
final List<AstNode> criticalNodes;
|
||||
|
||||
factory _CascadableExpression.fromExpressionStatement(
|
||||
ExpressionStatement statement) {
|
||||
var expression = statement.expression.unParenthesized;
|
||||
if (expression is AssignmentExpression) {
|
||||
return _CascadableExpression._fromAssignmentExpression(expression);
|
||||
}
|
||||
if (expression is MethodInvocation) {
|
||||
return _CascadableExpression._fromMethodInvocation(expression);
|
||||
}
|
||||
if (expression is CascadeExpression) {
|
||||
return _CascadableExpression._fromCascadeExpression(expression);
|
||||
}
|
||||
if (expression is PropertyAccess &&
|
||||
_isInvokedWithoutNullAwareOperator(expression.operator)) {
|
||||
return _CascadableExpression._fromPropertyAccess(expression);
|
||||
}
|
||||
if (expression is PrefixedIdentifier) {
|
||||
return _CascadableExpression._fromPrefixedIdentifier(expression);
|
||||
}
|
||||
return nullCascadableExpression;
|
||||
}
|
||||
|
||||
factory _CascadableExpression.fromVariableDeclarationStatement(
|
||||
VariableDeclarationStatement node) {
|
||||
var element = _getElementFromVariableDeclarationStatement(node);
|
||||
return _CascadableExpression._internal(element, [],
|
||||
canReceive: true, isCritical: true);
|
||||
}
|
||||
|
||||
factory _CascadableExpression._fromAssignmentExpression(
|
||||
AssignmentExpression node) {
|
||||
var leftExpression = node.leftHandSide.unParenthesized;
|
||||
if (leftExpression is SimpleIdentifier) {
|
||||
return _CascadableExpression._internal(
|
||||
leftExpression.staticElement?.canonicalElement, [node.rightHandSide],
|
||||
canReceive: node.operator.type != TokenType.QUESTION_QUESTION_EQ,
|
||||
isCritical: true);
|
||||
}
|
||||
// setters
|
||||
var variable = _getPrefixElementFromExpression(leftExpression);
|
||||
var canReceive = node.operator.type != TokenType.QUESTION_QUESTION_EQ &&
|
||||
variable is VariableElement &&
|
||||
!variable.isStatic;
|
||||
return _CascadableExpression._internal(variable, [node.rightHandSide],
|
||||
canJoin: true, canReceive: canReceive, 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 targetIsSimple = node.target is SimpleIdentifier;
|
||||
return _CascadableExpression._internal(
|
||||
_getTargetElementFromMethodInvocation(node), [node.argumentList],
|
||||
canJoin: targetIsSimple,
|
||||
canReceive: targetIsSimple,
|
||||
canBeCascaded: true);
|
||||
}
|
||||
return nullCascadableExpression;
|
||||
}
|
||||
|
||||
factory _CascadableExpression._fromPrefixedIdentifier(
|
||||
PrefixedIdentifier node) =>
|
||||
_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(node.target.canonicalElement, [],
|
||||
canJoin: targetIsSimple,
|
||||
canReceive: targetIsSimple,
|
||||
canBeCascaded: true);
|
||||
}
|
||||
|
||||
_CascadableExpression._internal(this.element, this.criticalNodes,
|
||||
{this.canJoin = false,
|
||||
this.canReceive = false,
|
||||
this.canBeCascaded = false,
|
||||
this.isCritical = false});
|
||||
|
||||
/// Whether `this` is compatible to be joined with [expressionBox] with a
|
||||
/// cascade operation.
|
||||
bool compatibleWith(_CascadableExpression expressionBox) =>
|
||||
element != null &&
|
||||
expressionBox.canReceive &&
|
||||
canJoin &&
|
||||
(canBeCascaded || expressionBox.canBeCascaded) &&
|
||||
element == expressionBox.element &&
|
||||
!_hasCriticalDependencies(expressionBox);
|
||||
|
||||
bool _hasCriticalDependencies(_CascadableExpression expressionBox) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitBlock(Block node) {
|
||||
if (node.statements.length < 2) {
|
||||
return;
|
||||
}
|
||||
var previousExpressionBox = _CascadableExpression.nullCascadableExpression;
|
||||
for (var statement in node.statements) {
|
||||
var currentExpressionBox = _CascadableExpression.nullCascadableExpression;
|
||||
if (statement is VariableDeclarationStatement) {
|
||||
currentExpressionBox =
|
||||
_CascadableExpression.fromVariableDeclarationStatement(statement);
|
||||
}
|
||||
if (statement is ExpressionStatement) {
|
||||
currentExpressionBox =
|
||||
_CascadableExpression.fromExpressionStatement(statement);
|
||||
}
|
||||
if (currentExpressionBox.compatibleWith(previousExpressionBox)) {
|
||||
rule.reportLint(statement);
|
||||
}
|
||||
previousExpressionBox = currentExpressionBox;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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 '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/type.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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
|
||||
and most of the time it is not what is expected.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
A? a;
|
||||
var v = a as B;
|
||||
var v = a as A;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
A? a;
|
||||
var v = a! as B;
|
||||
var v = a!;
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
class CastNullableToNonNullable extends LintRule {
|
||||
static const LintCode code = LintCode('cast_nullable_to_non_nullable',
|
||||
"Don't cast a nullable value to a non-nullable type.",
|
||||
correctionMessage:
|
||||
"Try adding a not-null assertion ('!') to make the type "
|
||||
'non-nullable.');
|
||||
|
||||
CastNullableToNonNullable()
|
||||
: super(
|
||||
name: 'cast_nullable_to_non_nullable',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style,
|
||||
);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
if (!context.isEnabled(Feature.non_nullable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addAsExpression(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
final LinterContext context;
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
@override
|
||||
void visitAsExpression(AsExpression node) {
|
||||
var expressionType = node.expression.staticType;
|
||||
var type = node.type.type;
|
||||
if (expressionType is! DynamicType &&
|
||||
expressionType is! InvalidType &&
|
||||
expressionType != null &&
|
||||
context.typeSystem.isNullable(expressionType) &&
|
||||
type != null &&
|
||||
context.typeSystem.isNonNullable(type)) {
|
||||
rule.reportLint(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
|
||||
import '../analyzer.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.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class A {
|
||||
IOSink _sinkA;
|
||||
void init(filename) {
|
||||
_sinkA = File(filename).openWrite(); // LINT
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void someFunction() {
|
||||
IOSink _sinkF; // LINT
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class B {
|
||||
IOSink _sinkB;
|
||||
void init(filename) {
|
||||
_sinkB = File(filename).openWrite(); // OK
|
||||
}
|
||||
|
||||
void dispose(filename) {
|
||||
_sinkB.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void someFunctionOK() {
|
||||
IOSink _sinkFOK; // OK
|
||||
_sinkFOK.close();
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
bool _isSink(DartType type) => type.implementsInterface('Sink', 'dart.core');
|
||||
|
||||
bool _isSocket(DartType type) => type.implementsInterface('Socket', 'dart.io');
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addFieldDeclaration(this, visitor);
|
||||
registry.addVariableDeclarationStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends LeakDetectorProcessors {
|
||||
static const _closeMethodName = 'close';
|
||||
static const _destroyMethodName = 'destroy';
|
||||
|
||||
@override
|
||||
Map<DartTypePredicate, String> predicates = {
|
||||
_isSink: _closeMethodName,
|
||||
_isSocket: _destroyMethodName
|
||||
};
|
||||
|
||||
_Visitor(super.rule);
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -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/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 {
|
||||
static const LintCode code = LintCode(
|
||||
'combinators_ordering', 'Sort combinator names alphabetically.',
|
||||
correctionMessage: 'Try sorting the combinator names alphabetically.');
|
||||
|
||||
CombinatorsOrdering()
|
||||
: super(
|
||||
name: 'combinators_ordering',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style,
|
||||
);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry,
|
||||
LinterContext context,
|
||||
) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addHideCombinator(this, visitor);
|
||||
registry.addShowCombinator(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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 [`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, assuming `outOfScopeId` is out of scope:
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
/// Return true if [value] is larger than [outOfScopeId].
|
||||
bool isOutOfRange(int value) { ... }
|
||||
```
|
||||
|
||||
**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),
|
||||
- two identifiers separated by a period where the first identifier is the name of a class that is in scope and the second is the name of a member declared in the class,
|
||||
- a single identifier followed by a pair of parentheses where the identifier is the name of a class that is in scope (used to refer to the unnamed constructor for the class), or
|
||||
- two identifiers separated by a period and followed by a pair of parentheses where the first identifier is the name of a class that is in scope and the second is the name of a named constructor (not strictly necessary, but allowed for consistency).
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addComment(this, visitor);
|
||||
registry.addCommentReference(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
for (var token in node.tokens) {
|
||||
if (!token.isSynthetic) {
|
||||
var comment = token.lexeme;
|
||||
var leftIndex = comment.indexOf('[');
|
||||
while (leftIndex >= 0) {
|
||||
var rightIndex = comment.indexOf(']', leftIndex);
|
||||
if (rightIndex >= 0) {
|
||||
var reference = comment.substring(leftIndex + 1, rightIndex);
|
||||
if (_isParserSpecialCase(reference)) {
|
||||
var nameOffset = token.offset + leftIndex + 1;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitCommentReference(CommentReference node) {
|
||||
var expression = node.expression;
|
||||
if (expression.isSynthetic) return;
|
||||
if (expression is Identifier &&
|
||||
expression.staticElement == null &&
|
||||
!links.contains(expression.name)) {
|
||||
rule.reportLint(expression);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isParserSpecialCase(String reference) =>
|
||||
reference == 'this' ||
|
||||
reference == 'null' ||
|
||||
reference == 'true' ||
|
||||
reference == 'false';
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 {
|
||||
static const LintCode code = LintCode('conditional_uri_does_not_exist',
|
||||
"The target of a conditional import doesn't exist.",
|
||||
correctionMessage:
|
||||
'Try creating the imported file or changing the URI to reference an '
|
||||
'existing file.');
|
||||
|
||||
ConditionalUriDoesNotExist()
|
||||
: super(
|
||||
name: 'conditional_uri_does_not_exist',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../extensions.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
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.
|
||||
|
||||
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;
|
||||
const defaultTimeout = 1000;
|
||||
final urlScheme = RegExp('^([a-z]+):');
|
||||
|
||||
class Dice {
|
||||
static final numberGenerator = Random();
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addDeclaredVariablePattern(this, visitor);
|
||||
registry.addEnumConstantDeclaration(this, visitor);
|
||||
registry.addTopLevelVariableDeclaration(this, visitor);
|
||||
registry.addVariableDeclarationList(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
void checkIdentifier(Token id) {
|
||||
var name = id.lexeme;
|
||||
if (!isLowerCamelCase(name)) {
|
||||
rule.reportLintForToken(id, arguments: [name]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
|
||||
if (node.parent.isFieldNameShortcut) return;
|
||||
checkIdentifier(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
|
||||
checkIdentifier(node.name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
||||
visitVariableDeclarationList(node.variables);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitVariableDeclarationList(VariableDeclarationList node) {
|
||||
for (var v in node.variables) {
|
||||
if (v.isConst) {
|
||||
checkIdentifier(v.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class BadReturn {
|
||||
double nonCompliantMethod() {
|
||||
try {
|
||||
return 1 / 0;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} finally {
|
||||
return 1.0; // LINT
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class BadContinue {
|
||||
double nonCompliantMethod() {
|
||||
for (var o in [1, 2]) {
|
||||
try {
|
||||
print(o / 0);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} finally {
|
||||
continue; // LINT
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class BadBreak {
|
||||
double nonCompliantMethod() {
|
||||
for (var o in [1, 2]) {
|
||||
try {
|
||||
print(o / 0);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} finally {
|
||||
break; // LINT
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class Ok {
|
||||
double compliantMethod() {
|
||||
var i = 5;
|
||||
try {
|
||||
i = 1 / 0;
|
||||
} catch (e) {
|
||||
print(e); // OK
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addBreakStatement(this, visitor);
|
||||
registry.addContinueStatement(this, visitor);
|
||||
registry.addReturnStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not extend this class, it is meant to be used from
|
||||
/// [ControlFlowInFinally] which is in a separate rule to allow a more granular
|
||||
/// configurability given that reporting throw statements in a finally clause is
|
||||
/// controversial.
|
||||
mixin ControlFlowInFinallyBlockReporter {
|
||||
LintRule get rule;
|
||||
|
||||
void reportIfFinallyAncestorExists(AstNode node,
|
||||
{required String kind, AstNode? ancestor}) {
|
||||
var tryStatement = node.thisOrAncestorOfType<TryStatement>();
|
||||
var finallyBlock = tryStatement?.finallyBlock;
|
||||
bool finallyBlockAncestorPredicate(AstNode n) => n == finallyBlock;
|
||||
if (tryStatement == null ||
|
||||
finallyBlock == null ||
|
||||
node.thisOrAncestorMatching(finallyBlockAncestorPredicate) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var enablerNode = _findEnablerNode(
|
||||
ancestor, finallyBlockAncestorPredicate, node, tryStatement);
|
||||
if (enablerNode == null) {
|
||||
rule.reportLint(node, arguments: [kind]);
|
||||
}
|
||||
}
|
||||
|
||||
AstNode? _findEnablerNode(
|
||||
AstNode? ancestor,
|
||||
bool Function(AstNode n) finallyBlockAncestorPredicate,
|
||||
AstNode node,
|
||||
TryStatement tryStatement) {
|
||||
AstNode? enablerNode;
|
||||
if (ancestor == null) {
|
||||
bool functionBlockPredicate(n) =>
|
||||
n is FunctionBody &&
|
||||
n.thisOrAncestorMatching(finallyBlockAncestorPredicate) != null;
|
||||
enablerNode = node.thisOrAncestorMatching(functionBlockPredicate);
|
||||
} else {
|
||||
enablerNode = ancestor.thisOrAncestorMatching((n) => n == tryStatement);
|
||||
}
|
||||
|
||||
return enablerNode;
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void>
|
||||
with ControlFlowInFinallyBlockReporter {
|
||||
@override
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitBreakStatement(BreakStatement node) {
|
||||
reportIfFinallyAncestorExists(node, ancestor: node.target, kind: 'break');
|
||||
}
|
||||
|
||||
@override
|
||||
void visitContinueStatement(ContinueStatement node) {
|
||||
reportIfFinallyAncestorExists(node,
|
||||
ancestor: node.target, kind: 'continue');
|
||||
}
|
||||
|
||||
@override
|
||||
void visitReturnStatement(ReturnStatement node) {
|
||||
reportIfFinallyAncestorExists(node, kind: 'return');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
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) {
|
||||
print('Bike to work!');
|
||||
} else {
|
||||
print('Go dancing or read a book!');
|
||||
}
|
||||
```
|
||||
|
||||
There is one exception to this: an `if` statement with no `else` clause where
|
||||
the entire `if` statement and the then body all fit in one line. In that case,
|
||||
you may leave off the braces if you prefer:
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
if (arg == null) return defaultValue;
|
||||
```
|
||||
|
||||
If the body wraps to the next line, though, use braces:
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
if (overflowChars != other.overflowChars) {
|
||||
return overflowChars < other.overflowChars;
|
||||
}
|
||||
```
|
||||
|
||||
''';
|
||||
|
||||
class CurlyBracesInFlowControlStructures extends LintRule {
|
||||
@override
|
||||
bool get canUseParsedResult => true;
|
||||
|
||||
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',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addDoStatement(this, visitor);
|
||||
registry.addForStatement(this, visitor);
|
||||
registry.addIfStatement(this, visitor);
|
||||
registry.addWhileStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitDoStatement(DoStatement node) {
|
||||
_check('a do', node.body);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForStatement(ForStatement node) {
|
||||
_check('a for', node.body);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
var elseStatement = node.elseStatement;
|
||||
if (elseStatement == null) {
|
||||
var parent = node.parent;
|
||||
if (parent is IfStatement && node == parent.elseStatement) {
|
||||
_check('an if', node.thenStatement);
|
||||
return;
|
||||
}
|
||||
if (node.thenStatement is Block) return;
|
||||
|
||||
var unit = node.root as CompilationUnit;
|
||||
var lineInfo = unit.lineInfo;
|
||||
if (lineInfo.getLocation(node.rightParenthesis.end).lineNumber !=
|
||||
lineInfo.getLocation(node.thenStatement.end).lineNumber) {
|
||||
rule.reportLint(node.thenStatement, arguments: ['an if']);
|
||||
}
|
||||
} else {
|
||||
_check('an if', node.thenStatement);
|
||||
if (elseStatement is! IfStatement) {
|
||||
_check('an if', elseStatement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitWhileStatement(WhileStatement node) {
|
||||
_check('a while', node.body);
|
||||
}
|
||||
|
||||
void _check(String where, Statement node) {
|
||||
if (node is! Block) rule.reportLint(node, arguments: [where]);
|
||||
}
|
||||
}
|
||||
@@ -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 presence 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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 deprecated annotation.';
|
||||
|
||||
const _details = r'''
|
||||
**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.
|
||||
- if a constructor parameter pointing to a field is deprecated, the field should
|
||||
also be deprecated.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
@deprecated
|
||||
class A {
|
||||
A();
|
||||
}
|
||||
|
||||
class B {
|
||||
B({this.field});
|
||||
@deprecated
|
||||
Object field;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
@deprecated
|
||||
class A {
|
||||
@deprecated
|
||||
A();
|
||||
}
|
||||
|
||||
class B {
|
||||
B({@deprecated this.field});
|
||||
@deprecated
|
||||
Object field;
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
C({@deprecated super.field});
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
class DeprecatedConsistency extends LintRule {
|
||||
static const LintCode constructorCode = LintCode('deprecated_consistency',
|
||||
'Constructors in a deprecated class should be deprecated.',
|
||||
correctionMessage: 'Try marking the constructor as deprecated.');
|
||||
|
||||
static const LintCode parameterCode = LintCode('deprecated_consistency',
|
||||
'Parameters that initialize a deprecated field should be deprecated.',
|
||||
correctionMessage: 'Try marking the parameter as deprecated.');
|
||||
|
||||
static const LintCode fieldCode = LintCode(
|
||||
'deprecated_consistency',
|
||||
'Fields that are initialized by a deprecated parameter should be '
|
||||
'deprecated.',
|
||||
correctionMessage: 'Try marking the field as deprecated.');
|
||||
|
||||
DeprecatedConsistency()
|
||||
: super(
|
||||
name: 'deprecated_consistency',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style,
|
||||
);
|
||||
|
||||
@override
|
||||
List<LintCode> get lintCodes => [constructorCode, parameterCode, fieldCode];
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addConstructorDeclaration(this, visitor);
|
||||
registry.addFieldFormalParameter(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||
var constructorElement = node.declaredElement;
|
||||
if (constructorElement != null &&
|
||||
constructorElement.enclosingElement.hasDeprecated &&
|
||||
!constructorElement.hasDeprecated) {
|
||||
rule.reportLint(node, errorCode: DeprecatedConsistency.constructorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldFormalParameter(FieldFormalParameter node) {
|
||||
var declaredElement = node.declaredElement;
|
||||
if (declaredElement is! FieldFormalParameterElement) return;
|
||||
|
||||
var field = declaredElement.field;
|
||||
if (field == null) return;
|
||||
|
||||
if (field.hasDeprecated && !declaredElement.hasDeprecated) {
|
||||
rule.reportLint(node, errorCode: DeprecatedConsistency.fieldCode);
|
||||
}
|
||||
if (!field.hasDeprecated && declaredElement.hasDeprecated) {
|
||||
rule.reportLintForOffset(field.nameOffset, field.nameLength,
|
||||
errorCode: DeprecatedConsistency.parameterCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
// Copyright (c) 2023, 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/syntactic_entity.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:analyzer/src/error/deprecated_member_use_verifier.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:analyzer/src/workspace/workspace.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
|
||||
const _desc =
|
||||
'Avoid using deprecated elements from within the package in which they are '
|
||||
'declared.';
|
||||
|
||||
const _details = r'''
|
||||
Elements that are annotated with `@Deprecated` should not be referenced from
|
||||
within the package in which they are declared.
|
||||
|
||||
**AVOID** using deprecated elements.
|
||||
|
||||
...
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
// Declared in one library:
|
||||
class Foo {
|
||||
@Deprecated("Use 'm2' instead")
|
||||
void m1() {}
|
||||
|
||||
void m2({
|
||||
@Deprecated('This is an old parameter') int? p,
|
||||
})
|
||||
}
|
||||
|
||||
@Deprecated('Do not use')
|
||||
int x = 0;
|
||||
|
||||
// In the same or another library, but within the same package:
|
||||
void m(Foo foo) {
|
||||
foo.m1();
|
||||
foo.m2(p: 7);
|
||||
x = 1;
|
||||
}
|
||||
```
|
||||
|
||||
Deprecated elements can be used from within _other_ deprecated elements, in
|
||||
order to allow for the deprecation of a collection of APIs together as one unit.
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
// Declared in one library:
|
||||
class Foo {
|
||||
@Deprecated("Use 'm2' instead")
|
||||
void m1() {}
|
||||
|
||||
void m2({
|
||||
@Deprecated('This is an old parameter') int? p,
|
||||
})
|
||||
}
|
||||
|
||||
@Deprecated('Do not use')
|
||||
int x = 0;
|
||||
|
||||
// In the same or another library, but within the same package:
|
||||
@Deprecated('Do not use')
|
||||
void m(Foo foo) {
|
||||
foo.m1();
|
||||
foo.m2(p: 7);
|
||||
x = 1;
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
class DeprecatedMemberUseFromSamePackage extends LintRule {
|
||||
static const LintCode code = LintCode(
|
||||
'deprecated_member_use_from_same_package',
|
||||
"'{0}' is deprecated and shouldn't be used.",
|
||||
correctionMessage:
|
||||
'Try replacing the use of the deprecated member with the replacement, '
|
||||
'if a replacement is specified.',
|
||||
);
|
||||
|
||||
static const LintCode codeWithMessage = LintCode(
|
||||
'deprecated_member_use_from_same_package',
|
||||
"'{0}' is deprecated and shouldn't be used. {1}",
|
||||
correctionMessage:
|
||||
'Try replacing the use of the deprecated member with the replacement, '
|
||||
'if a replacement is specified.',
|
||||
uniqueName: 'LintCode.deprecated_member_use_from_same_package_with_message',
|
||||
);
|
||||
|
||||
DeprecatedMemberUseFromSamePackage()
|
||||
: super(
|
||||
name: 'deprecated_member_use_from_same_package',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
List<LintCode> get lintCodes => [code, codeWithMessage];
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addCompilationUnit(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeprecatedMemberUseVerifier extends BaseDeprecatedMemberUseVerifier {
|
||||
final LintRule _rule;
|
||||
final WorkspacePackage _workspacePackage;
|
||||
|
||||
_DeprecatedMemberUseVerifier(this._rule, this._workspacePackage);
|
||||
|
||||
@override
|
||||
void reportError(SyntacticEntity errorEntity, Element element,
|
||||
String displayName, String? message) {
|
||||
var library = element is LibraryElement ? element : element.library;
|
||||
if (library == null || !_workspacePackage.contains(library.source)) {
|
||||
// In this case, `DEPRECATED_MEMBER_USE` is reported by the analyzer.
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedMessage = message?.trim();
|
||||
if (normalizedMessage == null ||
|
||||
normalizedMessage.isEmpty ||
|
||||
normalizedMessage == '.') {
|
||||
_rule.reportLintForOffset(
|
||||
errorEntity.offset,
|
||||
errorEntity.length,
|
||||
arguments: [displayName],
|
||||
errorCode: DeprecatedMemberUseFromSamePackage.code,
|
||||
);
|
||||
} else {
|
||||
if (!normalizedMessage.endsWith('.') &&
|
||||
!normalizedMessage.endsWith('?') &&
|
||||
!normalizedMessage.endsWith('!')) {
|
||||
normalizedMessage = '$message.';
|
||||
}
|
||||
_rule.reportLintForOffset(
|
||||
errorEntity.offset,
|
||||
errorEntity.length,
|
||||
arguments: [displayName, normalizedMessage],
|
||||
errorCode: DeprecatedMemberUseFromSamePackage.codeWithMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This visitor uses a [DeprecatedMemberUseVerifier] to both report uses of
|
||||
/// deprecated elements, and to track the deprecated-ness of ancestor
|
||||
/// declaration nodes.
|
||||
class _RecursiveVisitor extends RecursiveAstVisitor<void> {
|
||||
final _DeprecatedMemberUseVerifier _deprecatedVerifier;
|
||||
|
||||
_RecursiveVisitor(LintRule rule, WorkspacePackage package)
|
||||
: _deprecatedVerifier = _DeprecatedMemberUseVerifier(rule, package);
|
||||
|
||||
@override
|
||||
void visitAssignmentExpression(AssignmentExpression node) {
|
||||
_deprecatedVerifier.assignmentExpression(node);
|
||||
super.visitAssignmentExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBinaryExpression(BinaryExpression node) {
|
||||
_deprecatedVerifier.binaryExpression(node);
|
||||
super.visitBinaryExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitClassDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassTypeAlias(ClassTypeAlias node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitClassTypeAlias(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitCompilationUnit(CompilationUnit node) {
|
||||
var library = node.declaredElement?.library;
|
||||
if (library == null) {
|
||||
return;
|
||||
}
|
||||
_deprecatedVerifier.pushInDeprecatedValue(library.hasDeprecated);
|
||||
|
||||
super.visitCompilationUnit(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitConstructorDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConstructorName(ConstructorName node) {
|
||||
_deprecatedVerifier.constructorName(node);
|
||||
super.visitConstructorName(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDefaultFormalParameter(DefaultFormalParameter node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitDefaultFormalParameter(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitEnumDeclaration(EnumDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitEnumDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExportDirective(ExportDirective node) {
|
||||
_deprecatedVerifier.exportDirective(node);
|
||||
super.visitExportDirective(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitExtensionDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExtensionOverride(ExtensionOverride node) {
|
||||
_deprecatedVerifier.extensionOverride(node);
|
||||
super.visitExtensionOverride(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldDeclaration(FieldDeclaration node) {
|
||||
_deprecatedVerifier.pushInDeprecatedMetadata(node.metadata);
|
||||
|
||||
try {
|
||||
super.visitFieldDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldFormalParameter(FieldFormalParameter node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitFieldFormalParameter(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitFunctionDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
|
||||
_deprecatedVerifier.functionExpressionInvocation(node);
|
||||
super.visitFunctionExpressionInvocation(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitFunctionTypeAlias(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitGenericTypeAlias(GenericTypeAlias node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitGenericTypeAlias(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitImportDirective(ImportDirective node) {
|
||||
_deprecatedVerifier.importDirective(node);
|
||||
super.visitImportDirective(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIndexExpression(IndexExpression node) {
|
||||
_deprecatedVerifier.indexExpression(node);
|
||||
super.visitIndexExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||
_deprecatedVerifier.instanceCreationExpression(node);
|
||||
super.visitInstanceCreationExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitMethodDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
_deprecatedVerifier.methodInvocation(node);
|
||||
super.visitMethodInvocation(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMixinDeclaration(MixinDeclaration node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitMixinDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitNamedType(NamedType node) {
|
||||
_deprecatedVerifier.namedType(node);
|
||||
super.visitNamedType(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPostfixExpression(PostfixExpression node) {
|
||||
_deprecatedVerifier.postfixExpression(node);
|
||||
super.visitPostfixExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPrefixExpression(PrefixExpression node) {
|
||||
_deprecatedVerifier.prefixExpression(node);
|
||||
super.visitPrefixExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitRedirectingConstructorInvocation(
|
||||
RedirectingConstructorInvocation node) {
|
||||
_deprecatedVerifier.redirectingConstructorInvocation(node);
|
||||
super.visitRedirectingConstructorInvocation(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleFormalParameter(SimpleFormalParameter node) {
|
||||
_deprecatedVerifier
|
||||
.pushInDeprecatedValue(node.declaredElement?.hasDeprecated ?? false);
|
||||
|
||||
try {
|
||||
super.visitSimpleFormalParameter(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleIdentifier(SimpleIdentifier node) {
|
||||
_deprecatedVerifier.simpleIdentifier(node);
|
||||
super.visitSimpleIdentifier(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
|
||||
_deprecatedVerifier.superConstructorInvocation(node);
|
||||
super.visitSuperConstructorInvocation(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
||||
_deprecatedVerifier.pushInDeprecatedMetadata(node.metadata);
|
||||
|
||||
try {
|
||||
super.visitTopLevelVariableDeclaration(node);
|
||||
} finally {
|
||||
_deprecatedVerifier.popInDeprecated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This [SimpleAstVisitor] visits the [CompilationUnit], and forwards the
|
||||
/// remainder of visitations to [_RecursiveVisitor], which keeps track of
|
||||
/// the deprecated-ness of ancestor declaration nodes.
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule _rule;
|
||||
final LinterContext _context;
|
||||
|
||||
_Visitor(this._rule, this._context);
|
||||
|
||||
@override
|
||||
void visitCompilationUnit(CompilationUnit node) {
|
||||
var package = _context.package;
|
||||
if (package == null) {
|
||||
// If we don't appear to be in any known package structure, then we can
|
||||
// never report that a deprecated use is from the same package as the
|
||||
// declaration.
|
||||
return;
|
||||
}
|
||||
var visitor = _RecursiveVisitor(_rule, package);
|
||||
node.accept(visitor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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.
|
||||
|
||||
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 '../extensions.dart';
|
||||
import '../util/flutter_utils.dart';
|
||||
|
||||
const _desc = r'DO reference all public properties in debug methods.';
|
||||
|
||||
const _details = r'''
|
||||
**DO** reference all public properties in `debug` method implementations.
|
||||
|
||||
Implementers of `Diagnosticable` should reference all public properties in
|
||||
a `debugFillProperties(...)` or `debugDescribeChildren(...)` method
|
||||
implementation to improve debuggability at runtime.
|
||||
|
||||
Public properties are defined as fields and getters that are
|
||||
|
||||
* not package-private (e.g., prefixed with `_`)
|
||||
* not `static` or overriding
|
||||
* not themselves `Widget`s or collections of `Widget`s
|
||||
|
||||
In addition, the "debug" prefix is treated specially for properties in Flutter.
|
||||
For the purposes of diagnostics, a property `foo` and a prefixed property
|
||||
`debugFoo` are treated as effectively describing the same property and it is
|
||||
sufficient to refer to one or the other.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
class Absorber extends Widget {
|
||||
bool get absorbing => _absorbing;
|
||||
bool _absorbing;
|
||||
bool get ignoringSemantics => _ignoringSemantics;
|
||||
bool _ignoringSemantics;
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
|
||||
// Missing reference to ignoringSemantics
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
class Absorber extends Widget {
|
||||
bool get absorbing => _absorbing;
|
||||
bool _absorbing;
|
||||
bool get ignoringSemantics => _ignoringSemantics;
|
||||
bool _ignoringSemantics;
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
|
||||
properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics));
|
||||
}
|
||||
}
|
||||
```
|
||||
''';
|
||||
|
||||
class DiagnosticDescribeAllProperties extends LintRule {
|
||||
static const LintCode code = LintCode(
|
||||
'diagnostic_describe_all_properties',
|
||||
"The public property isn't described by either 'debugFillProperties' or "
|
||||
"'debugDescribeChildren'.",
|
||||
correctionMessage: 'Try describing the property.');
|
||||
|
||||
DiagnosticDescribeAllProperties()
|
||||
: super(
|
||||
name: 'diagnostic_describe_all_properties',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors,
|
||||
);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addClassDeclaration(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
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<Token> properties) {
|
||||
method?.body.accept(_IdentifierVisitor(properties));
|
||||
}
|
||||
|
||||
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 (!type.implementsInterface('Diagnosticable', '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = <Token>[];
|
||||
for (var member in node.members) {
|
||||
if (member is MethodDeclaration && member.isGetter) {
|
||||
if (!member.isStatic &&
|
||||
!skipForDiagnostic(
|
||||
element: member.declaredElement,
|
||||
name: member.name,
|
||||
type: member.returnType?.type,
|
||||
)) {
|
||||
properties.add(member.name);
|
||||
}
|
||||
} else if (member is FieldDeclaration) {
|
||||
for (var v in member.fields.variables) {
|
||||
var declaredElement = v.declaredElement;
|
||||
if (declaredElement != null &&
|
||||
!declaredElement.isStatic &&
|
||||
!skipForDiagnostic(
|
||||
element: declaredElement,
|
||||
name: v.name,
|
||||
type: declaredElement.type,
|
||||
)) {
|
||||
properties.add(v.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var debugFillProperties = node.members.getMethod('debugFillProperties');
|
||||
var debugDescribeChildren = node.members.getMethod('debugDescribeChildren');
|
||||
|
||||
// Remove any defined in debugFillProperties.
|
||||
removeReferences(debugFillProperties, properties);
|
||||
|
||||
// Remove any defined in debugDescribeChildren.
|
||||
removeReferences(debugDescribeChildren, properties);
|
||||
|
||||
// Flag the rest.
|
||||
properties.forEach(rule.reportLintForToken);
|
||||
}
|
||||
|
||||
bool _isOverridingMember(Element? member) {
|
||||
if (member == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var classElement = member.thisOrAncestorOfType<ClassElement>();
|
||||
if (classElement == null) {
|
||||
return false;
|
||||
}
|
||||
var name = member.name;
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var libraryUri = classElement.library.source.uri;
|
||||
return context.inheritanceManager
|
||||
.getInherited(classElement.thisType, Name(libraryUri, name)) !=
|
||||
null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
// Copyright (c) 2017, 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'Adhere to Effective Dart Guide directives sorting conventions.';
|
||||
const _details = r'''
|
||||
**DO** follow the directive ordering conventions in
|
||||
[Effective Dart](https://dart.dev/effective-dart/style#ordering):
|
||||
|
||||
**DO** place `dart:` imports before other imports.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'package:bar/bar.dart';
|
||||
import 'package:foo/foo.dart';
|
||||
|
||||
import 'dart:async'; // LINT
|
||||
import 'dart:html'; // LINT
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'dart:html'; // OK
|
||||
import 'package:bar/bar.dart';
|
||||
|
||||
import 'dart:async'; // LINT
|
||||
import 'package:foo/foo.dart';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'dart:async'; // OK
|
||||
import 'dart:html'; // OK
|
||||
|
||||
import 'package:bar/bar.dart';
|
||||
import 'package:foo/foo.dart';
|
||||
```
|
||||
|
||||
**DO** place `package:` imports before relative imports.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'a.dart';
|
||||
import 'b.dart';
|
||||
|
||||
import 'package:bar/bar.dart'; // LINT
|
||||
import 'package:foo/foo.dart'; // LINT
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'package:bar/bar.dart'; // OK
|
||||
import 'a.dart';
|
||||
|
||||
import 'package:foo/foo.dart'; // LINT
|
||||
import 'b.dart';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'package:bar/bar.dart'; // OK
|
||||
import 'package:foo/foo.dart'; // OK
|
||||
|
||||
import 'a.dart';
|
||||
import 'b.dart';
|
||||
```
|
||||
|
||||
**DO** specify exports in a separate section after all imports.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'src/error.dart';
|
||||
export 'src/error.dart'; // LINT
|
||||
import 'src/string_source.dart';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'src/error.dart';
|
||||
import 'src/string_source.dart';
|
||||
|
||||
export 'src/error.dart'; // OK
|
||||
```
|
||||
|
||||
**DO** sort sections alphabetically.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
import 'package:foo/bar.dart'; // OK
|
||||
import 'package:bar/bar.dart'; // LINT
|
||||
|
||||
import 'a/b.dart'; // OK
|
||||
import 'a.dart'; // LINT
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
import 'package:bar/bar.dart'; // OK
|
||||
import 'package:foo/bar.dart'; // OK
|
||||
|
||||
import 'a.dart'; // OK
|
||||
import 'a/b.dart'; // OK
|
||||
```
|
||||
''';
|
||||
|
||||
const _exportKeyword = 'export';
|
||||
|
||||
const _importKeyword = 'import';
|
||||
|
||||
/// Compares directives by package then file in package.
|
||||
///
|
||||
/// Package is everything until the first `/`.
|
||||
int compareDirectives(String a, String b) {
|
||||
if (!a.startsWith('package:') || !b.startsWith('package:')) {
|
||||
return a.compareTo(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.uri.stringValue;
|
||||
return uriContent != null && uriContent.contains(':');
|
||||
}
|
||||
|
||||
bool _isDartDirective(NamespaceDirective node) {
|
||||
var uriContent = node.uri.stringValue;
|
||||
return uriContent != null && uriContent.startsWith('dart:');
|
||||
}
|
||||
|
||||
bool _isExportDirective(Directive node) => node is ExportDirective;
|
||||
|
||||
bool _isNotDartDirective(NamespaceDirective node) => !_isDartDirective(node);
|
||||
|
||||
bool _isPackageDirective(NamespaceDirective node) {
|
||||
var uriContent = node.uri.stringValue;
|
||||
return uriContent != null && uriContent.startsWith('package:');
|
||||
}
|
||||
|
||||
bool _isPartDirective(Directive node) => node is PartDirective;
|
||||
|
||||
bool _isRelativeDirective(NamespaceDirective node) =>
|
||||
!_isAbsoluteDirective(node);
|
||||
|
||||
class DirectivesOrdering extends LintRule {
|
||||
static const LintCode dartDirectiveGoFirst = LintCode(
|
||||
'directives_ordering', "Place 'dart:' {0}s before other {0}s.",
|
||||
correctionMessage: 'Try sorting the directives.');
|
||||
|
||||
static const LintCode directiveSectionOrderedAlphabetically = LintCode(
|
||||
'directives_ordering', 'Sort directive sections alphabetically.',
|
||||
correctionMessage: 'Try sorting the directives.');
|
||||
|
||||
static const LintCode exportDirectiveAfterImportDirectives = LintCode(
|
||||
'directives_ordering',
|
||||
'Specify exports in a separate section after all imports.',
|
||||
correctionMessage: 'Try sorting the directives.');
|
||||
|
||||
static const LintCode packageDirectiveBeforeRelative = LintCode(
|
||||
'directives_ordering', "Place 'package:' {0}s before relative {0}s.",
|
||||
correctionMessage: 'Try sorting the directives.');
|
||||
|
||||
DirectivesOrdering()
|
||||
: super(
|
||||
name: 'directives_ordering',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.style);
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry, LinterContext context) {
|
||||
var visitor = _Visitor(this);
|
||||
registry.addCompilationUnit(this, visitor);
|
||||
}
|
||||
|
||||
void _reportLintWithDartDirectiveGoFirstMessage(AstNode node, String type) {
|
||||
reportLint(node,
|
||||
errorCode: DirectivesOrdering.dartDirectiveGoFirst, arguments: [type]);
|
||||
}
|
||||
|
||||
void _reportLintWithDirectiveSectionOrderedAlphabeticallyMessage(
|
||||
AstNode node) {
|
||||
reportLint(node,
|
||||
errorCode: DirectivesOrdering.directiveSectionOrderedAlphabetically);
|
||||
}
|
||||
|
||||
void _reportLintWithExportDirectiveAfterImportDirectiveMessage(AstNode node) {
|
||||
reportLint(node,
|
||||
errorCode: DirectivesOrdering.exportDirectiveAfterImportDirectives);
|
||||
}
|
||||
|
||||
void _reportLintWithPackageDirectiveBeforeRelativeMessage(
|
||||
AstNode node, String 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.uri.stringValue;
|
||||
return uriContent != null &&
|
||||
uriContent.startsWith('package:$_packageName/');
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final DirectivesOrdering rule;
|
||||
|
||||
_Visitor(this.rule);
|
||||
|
||||
@override
|
||||
void visitCompilationUnit(CompilationUnit node) {
|
||||
var lintedNodes = <AstNode>{};
|
||||
_checkDartDirectiveGoFirst(lintedNodes, node);
|
||||
_checkPackageDirectiveBeforeRelative(lintedNodes, node);
|
||||
_checkExportDirectiveAfterImportDirective(lintedNodes, node);
|
||||
_checkDirectiveSectionOrderedAlphabetically(lintedNodes, node);
|
||||
}
|
||||
|
||||
void _checkDartDirectiveGoFirst(
|
||||
Set<AstNode> lintedNodes, CompilationUnit node) {
|
||||
void reportImport(NamespaceDirective directive) {
|
||||
if (lintedNodes.add(directive)) {
|
||||
rule._reportLintWithDartDirectiveGoFirstMessage(
|
||||
directive, _importKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
void reportExport(NamespaceDirective directive) {
|
||||
if (lintedNodes.add(directive)) {
|
||||
rule._reportLintWithDartDirectiveGoFirstMessage(
|
||||
directive, _exportKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<NamespaceDirective> getNodesToLint(
|
||||
Iterable<NamespaceDirective> directives) =>
|
||||
directives.skipWhile(_isDartDirective).where(_isDartDirective);
|
||||
|
||||
getNodesToLint(_getImportDirectives(node)).forEach(reportImport);
|
||||
|
||||
getNodesToLint(_getExportDirectives(node)).forEach(reportExport);
|
||||
}
|
||||
|
||||
void _checkDirectiveSectionOrderedAlphabetically(
|
||||
Set<AstNode> lintedNodes, CompilationUnit node) {
|
||||
var importDirectives = _getImportDirectives(node);
|
||||
var exportDirectives = _getExportDirectives(node);
|
||||
|
||||
var dartImports = importDirectives.where(_isDartDirective);
|
||||
var dartExports = exportDirectives.where(_isDartDirective);
|
||||
|
||||
var relativeImports = importDirectives.where(_isRelativeDirective);
|
||||
var relativeExports = exportDirectives.where(_isRelativeDirective);
|
||||
|
||||
_checkSectionInOrder(lintedNodes, dartImports);
|
||||
_checkSectionInOrder(lintedNodes, dartExports);
|
||||
|
||||
_checkSectionInOrder(lintedNodes, relativeImports);
|
||||
_checkSectionInOrder(lintedNodes, relativeExports);
|
||||
|
||||
// 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.
|
||||
|
||||
// Not a pub package. Package directives should be sorted in one block.
|
||||
var packageImports = importDirectives.where(_isPackageDirective);
|
||||
var packageExports = exportDirectives.where(_isPackageDirective);
|
||||
|
||||
_checkSectionInOrder(lintedNodes, packageImports);
|
||||
_checkSectionInOrder(lintedNodes, packageExports);
|
||||
|
||||
// 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(
|
||||
Set<AstNode> lintedNodes, CompilationUnit node) {
|
||||
void reportDirective(Directive directive) {
|
||||
if (lintedNodes.add(directive)) {
|
||||
rule._reportLintWithExportDirectiveAfterImportDirectiveMessage(
|
||||
directive);
|
||||
}
|
||||
}
|
||||
|
||||
node.directives.reversed
|
||||
.skipWhile(_isPartDirective)
|
||||
.skipWhile(_isExportDirective)
|
||||
.where(_isExportDirective)
|
||||
.forEach(reportDirective);
|
||||
}
|
||||
|
||||
void _checkPackageDirectiveBeforeRelative(
|
||||
Set<AstNode> lintedNodes, CompilationUnit node) {
|
||||
void reportImport(NamespaceDirective directive) {
|
||||
if (lintedNodes.add(directive)) {
|
||||
rule._reportLintWithPackageDirectiveBeforeRelativeMessage(
|
||||
directive, _importKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
void reportExport(NamespaceDirective directive) {
|
||||
if (lintedNodes.add(directive)) {
|
||||
rule._reportLintWithPackageDirectiveBeforeRelativeMessage(
|
||||
directive, _exportKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<NamespaceDirective> getNodesToLint(
|
||||
Iterable<NamespaceDirective> directives) =>
|
||||
directives
|
||||
.where(_isNotDartDirective)
|
||||
.skipWhile(_isAbsoluteDirective)
|
||||
.where(_isPackageDirective);
|
||||
|
||||
getNodesToLint(_getImportDirectives(node)).forEach(reportImport);
|
||||
|
||||
getNodesToLint(_getExportDirectives(node)).forEach(reportExport);
|
||||
}
|
||||
|
||||
void _checkSectionInOrder(
|
||||
Set<AstNode> lintedNodes, Iterable<NamespaceDirective> nodes) {
|
||||
void reportDirective(NamespaceDirective directive) {
|
||||
if (lintedNodes.add(directive)) {
|
||||
rule._reportLintWithDirectiveSectionOrderedAlphabeticallyMessage(
|
||||
directive);
|
||||
}
|
||||
}
|
||||
|
||||
NamespaceDirective? previousDirective;
|
||||
for (var directive in nodes) {
|
||||
if (previousDirective != null) {
|
||||
var previousUri = previousDirective.uri.stringValue;
|
||||
var directiveUri = directive.uri.stringValue;
|
||||
if (previousUri != null &&
|
||||
directiveUri != null &&
|
||||
compareDirectives(previousUri, directiveUri) > 0) {
|
||||
reportDirective(directive);
|
||||
}
|
||||
}
|
||||
previousDirective = directive;
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<ExportDirective> _getExportDirectives(CompilationUnit node) =>
|
||||
node.directives.whereType<ExportDirective>();
|
||||
|
||||
Iterable<ImportDirective> _getImportDirectives(CompilationUnit node) =>
|
||||
node.directives.whereType<ImportDirective>();
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// 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 {
|
||||
static const LintCode code = LintCode('discarded_futures',
|
||||
"Asynchronous function invoked in a non-'async' function.",
|
||||
correctionMessage:
|
||||
"Try converting the enclosing function to be 'async' and then "
|
||||
"'await' the future.");
|
||||
|
||||
DiscardedFutures()
|
||||
: super(
|
||||
name: 'discarded_futures',
|
||||
description: _desc,
|
||||
details: _details,
|
||||
group: Group.errors);
|
||||
|
||||
@override
|
||||
LintCode get lintCode => code;
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user