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:
William Hesse
2023-08-23 13:54:00 +02:00
638 changed files with 86712 additions and 0 deletions
+5
View File
@@ -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.
+22
View File
@@ -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.
+50
View File
@@ -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 lints 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
View File
@@ -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)
+10
View File
@@ -0,0 +1,10 @@
# Dependabot configuration file.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
labels:
- autosubmit
+10
View File
@@ -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)'
+104
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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!
+54
View File
@@ -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
+13
View File
@@ -0,0 +1,13 @@
.idea
.dart_tool
packages
build
.project
.settings
pubspec.lock
.packages
linter.iml
.atom
/.vscode
/.history
/doc/lints/
+50
View File
@@ -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
+87
View File
@@ -0,0 +1,87 @@
Contributing to the Linter
==========================
[![Build Status](https://github.com/dart-lang/linter/workflows/linter/badge.svg)](https://github.com/dart-lang/linter/actions)
![GitHub contributors](https://img.shields.io/github/contributors/dart-lang/linter.svg)
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).
+27
View File
@@ -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.
+99
View File
@@ -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].
[![Build Status](https://github.com/dart-lang/linter/workflows/linter/badge.svg)](https://github.com/dart-lang/linter/actions)
[![Coverage Status](https://coveralls.io/repos/dart-lang/linter/badge.svg)](https://coveralls.io/r/dart-lang/linter)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/dart-lang/linter/badge)](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
+67
View File
@@ -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
+9
View File
@@ -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);
}
+79
View File
@@ -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.
+169
View File
@@ -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
+14
View File
@@ -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).
+220
View File
@@ -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
+177
View File
@@ -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);
}
}
+470
View File
@@ -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;
}
}
+178
View File
@@ -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''');
}
}
+505
View File
@@ -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
};
}
+341
View File
@@ -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;
}
+464
View File
@@ -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);
}
}
}
+61
View File
@@ -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;
}
}
+125
View File
@@ -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);
}
}
}
+100
View File
@@ -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
View File
@@ -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