From 3429f0296aab3fa53087bdaca8a2c3cdc13e1b47 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 7 Mar 2023 15:54:03 +0100 Subject: [PATCH 01/16] Feat: Add dart_grog_lint package --- .github/workflows/dart_frog_lint.yaml | 53 ++++ docs/docs/basics/resources.md | 10 +- docs/docs/overview.md | 2 +- docs/docs/tutorials/todos.md | 14 +- docs/package.json | 2 +- docs/yarn.lock | 34 +-- packages/dart_frog_cli/CHANGELOG.md | 5 + .../lib/src/commands/dev/dev.dart | 5 +- packages/dart_frog_cli/lib/src/version.dart | 2 +- packages/dart_frog_cli/pubspec.yaml | 4 +- packages/dart_frog_lint/.gitignore | 10 + packages/dart_frog_lint/CHANGELOG.md | 3 + packages/dart_frog_lint/LICENSE | 21 ++ packages/dart_frog_lint/README.md | 230 ++++++++++++++++++ packages/dart_frog_lint/analysis_options.yaml | 1 + packages/dart_frog_lint/coverage_badge.svg | 20 ++ packages/dart_frog_lint/example/.gitignore | 1 + .../example/analysis_options.yaml | 7 + packages/dart_frog_lint/example/lib/out.dart | 0 packages/dart_frog_lint/example/main.dart | 7 + packages/dart_frog_lint/example/pubspec.yaml | 15 ++ .../example/routes/async_response.dart | 5 + .../example/routes/invalid_context.dart | 6 + .../routes/invalid_parameter_middleware.dart | 6 + .../example/routes/invalid_response.dart | 6 + .../routes/invalid_return_middleware.dart | 6 + .../example/routes/missing_middleware.dart | 4 + .../example/routes/missing_on_request.dart | 4 + .../example/routes/no_context.dart | 6 + .../routes/no_parameter_middleware.dart | 6 + .../routes/parametrized_route/[userId2].dart | 7 + .../routes/parametrized_route/[userId].dart | 7 + .../example/routes/sync_response.dart | 5 + .../example/routes/valid_middleware.dart | 5 + .../dart_frog_lint/lib/dart_frog_lint.dart | 16 ++ .../lib/src/dart_frog_entrypoint.dart | 90 +++++++ .../lib/src/dart_frog_middleware.dart | 65 +++++ .../lib/src/dart_frog_route.dart | 89 +++++++ .../dart_frog_lint/lib/src/parse_route.dart | 43 ++++ packages/dart_frog_lint/lib/src/types.dart | 46 ++++ packages/dart_frog_lint/pubspec.yaml | 23 ++ 41 files changed, 854 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/dart_frog_lint.yaml create mode 100644 packages/dart_frog_lint/.gitignore create mode 100644 packages/dart_frog_lint/CHANGELOG.md create mode 100644 packages/dart_frog_lint/LICENSE create mode 100644 packages/dart_frog_lint/README.md create mode 100644 packages/dart_frog_lint/analysis_options.yaml create mode 100644 packages/dart_frog_lint/coverage_badge.svg create mode 100644 packages/dart_frog_lint/example/.gitignore create mode 100644 packages/dart_frog_lint/example/analysis_options.yaml create mode 100644 packages/dart_frog_lint/example/lib/out.dart create mode 100644 packages/dart_frog_lint/example/main.dart create mode 100644 packages/dart_frog_lint/example/pubspec.yaml create mode 100644 packages/dart_frog_lint/example/routes/async_response.dart create mode 100644 packages/dart_frog_lint/example/routes/invalid_context.dart create mode 100644 packages/dart_frog_lint/example/routes/invalid_parameter_middleware.dart create mode 100644 packages/dart_frog_lint/example/routes/invalid_response.dart create mode 100644 packages/dart_frog_lint/example/routes/invalid_return_middleware.dart create mode 100644 packages/dart_frog_lint/example/routes/missing_middleware.dart create mode 100644 packages/dart_frog_lint/example/routes/missing_on_request.dart create mode 100644 packages/dart_frog_lint/example/routes/no_context.dart create mode 100644 packages/dart_frog_lint/example/routes/no_parameter_middleware.dart create mode 100644 packages/dart_frog_lint/example/routes/parametrized_route/[userId2].dart create mode 100644 packages/dart_frog_lint/example/routes/parametrized_route/[userId].dart create mode 100644 packages/dart_frog_lint/example/routes/sync_response.dart create mode 100644 packages/dart_frog_lint/example/routes/valid_middleware.dart create mode 100644 packages/dart_frog_lint/lib/dart_frog_lint.dart create mode 100644 packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart create mode 100644 packages/dart_frog_lint/lib/src/dart_frog_middleware.dart create mode 100644 packages/dart_frog_lint/lib/src/dart_frog_route.dart create mode 100644 packages/dart_frog_lint/lib/src/parse_route.dart create mode 100644 packages/dart_frog_lint/lib/src/types.dart create mode 100644 packages/dart_frog_lint/pubspec.yaml diff --git a/.github/workflows/dart_frog_lint.yaml b/.github/workflows/dart_frog_lint.yaml new file mode 100644 index 000000000..bbc200ed3 --- /dev/null +++ b/.github/workflows/dart_frog_lint.yaml @@ -0,0 +1,53 @@ +name: dart_frog_lint + +on: + pull_request: + paths: + - ".github/workflows/dart_frog_lint.yaml" + - "packages/dart_frog_lint/example/**" + - "packages/dart_frog_lint/lib/**" + - "packages/dart_frog_lint/test/**" + - "packages/dart_frog_lint/pubspec.yaml" + push: + branches: + - main + paths: + - ".github/workflows/dart_frog_lint.yaml" + - "packages/dart_frog_lint/example/**" + - "packages/dart_frog_lint/lib/**" + - "packages/dart_frog_lint/test/**" + - "packages/dart_frog_lint/pubspec.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 + with: + working_directory: packages/dart_frog + + custom_lint: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/dart_frog_lint + + steps: + # Bootstrap project + - uses: actions/checkout@v3.1.0 + with: + fetch-depth: 2 + - uses: subosito/flutter-action@v2.7.1 + - name: Add pub cache bin to PATH + run: echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH + - name: Add pub cache to PATH + run: echo "PUB_CACHE="$HOME/.pub-cache"" >> $GITHUB_ENV + - name: Install dependencies + run: flutter pub get + + # Finally do some checks + - name: Run custom_lint + run: dart run custom_lint + + pana: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/pana.yml@v1 + with: + working_directory: packages/dart_frog diff --git a/docs/docs/basics/resources.md b/docs/docs/basics/resources.md index 3fe790094..6dd8ff568 100644 --- a/docs/docs/basics/resources.md +++ b/docs/docs/basics/resources.md @@ -5,18 +5,12 @@ title: 📚 Additional Resources # Additional Resources 📚 -- [Examples][examples_link] -- [Roadmap][roadmap_link] -- [Blog post][blog_link] -- [Livestream demo][livestream_link] +- [Awesome Dart Frog repo][awesome_dart_frog_link]: Highlights awesome Dart Frog resources — articles, videos, open source projects, and more! :::info Fun fact: Did you know the [dart2js][dart2js_compiler_link] compiler [used to be called frog][dart2js_frog_pr_link]? ::: -[blog_link]: https://verygood.ventures/blog/dart-frog [dart2js_compiler_link]: https://dart.dev/tools/dart2js [dart2js_frog_pr_link]: https://github.com/dart-lang/sdk/issues/2194 -[examples_link]: https://github.com/VeryGoodOpenSource/dart_frog/tree/main/examples -[livestream_link]: https://youtu.be/N7l0b09c6DA -[roadmap_link]: /docs/roadmap +[awesome_dart_frog_link]: https://github.com/VeryGoodOpenSource/awesome-dart-frog diff --git a/docs/docs/overview.md b/docs/docs/overview.md index 9c98c4e28..9555b4713 100644 --- a/docs/docs/overview.md +++ b/docs/docs/overview.md @@ -18,7 +18,7 @@ Dart Frog provides a simple core with a small API surface area in order to reduc In order to use Dart Frog you must have the [Dart SDK][dart_installation_link] installed on your machine. :::info -Dart Frog requires Dart `">=2.17.0 <3.0.0"` +Dart Frog requires Dart `">=2.19.0 <3.0.0"` ::: ### Installing 🧑‍💻 diff --git a/docs/docs/tutorials/todos.md b/docs/docs/tutorials/todos.md index 92c8891ef..748fa0b2f 100644 --- a/docs/docs/tutorials/todos.md +++ b/docs/docs/tutorials/todos.md @@ -167,7 +167,7 @@ version: 0.1.0+1 publish_to: none environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=2.19.0 <3.0.0' dependencies: equatable: ^2.0.3 @@ -179,7 +179,7 @@ dev_dependencies: json_serializable: ^6.3.1 mocktail: ^0.3.0 test: ^1.19.2 - very_good_analysis: ^3.1.0 + very_good_analysis: ^4.0.0 ``` Install the newly added dependencies via: @@ -368,7 +368,7 @@ version: 0.1.0+1 publish_to: none environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=2.19.0 <3.0.0' dependencies: todos_data_source: @@ -378,7 +378,7 @@ dependencies: dev_dependencies: mocktail: ^0.3.0 test: ^1.19.2 - very_good_analysis: ^3.1.0 + very_good_analysis: ^4.0.0 ``` :::note @@ -445,10 +445,10 @@ version: 1.0.0+1 publish_to: none environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=2.19.0 <3.0.0' dependencies: - dart_frog: ^0.0.1-dev + dart_frog: ^0.3.0 in_memory_todos_data_source: path: packages/in_memory_todos_data_source todos_data_source: @@ -457,7 +457,7 @@ dependencies: dev_dependencies: mocktail: ^0.3.0 test: ^1.19.2 - very_good_analysis: ^3.1.0 + very_good_analysis: ^4.0.0 ``` Install the newly added dependencies via: diff --git a/docs/package.json b/docs/package.json index bbc806a2e..6488201c7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -29,7 +29,7 @@ "@babel/eslint-parser": "^7.19.1", "@docusaurus/eslint-plugin": "^2.3.1", "@docusaurus/module-type-aliases": "^2.3.1", - "eslint": "^8.34.0", + "eslint": "^8.35.0", "prettier": "^2.8.4" }, "browserslist": { diff --git a/docs/yarn.lock b/docs/yarn.lock index 74380223a..660f71221 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1775,10 +1775,10 @@ url-loader "^4.1.1" webpack "^5.73.0" -"@eslint/eslintrc@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" - integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== +"@eslint/eslintrc@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.0.tgz#943309d8697c52fc82c076e90c1c74fbbe69dbff" + integrity sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1790,6 +1790,11 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/js@8.35.0": + version "8.35.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.35.0.tgz#b7569632b0b788a0ca0e438235154e45d42813a7" + integrity sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw== + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -3867,12 +3872,13 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.34.0: - version "8.34.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6" - integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg== +eslint@^8.35.0: + version "8.35.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.35.0.tgz#fffad7c7e326bae606f0e8f436a6158566d42323" + integrity sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw== dependencies: - "@eslint/eslintrc" "^1.4.1" + "@eslint/eslintrc" "^2.0.0" + "@eslint/js" "8.35.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -3886,7 +3892,7 @@ eslint@^8.34.0: eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" espree "^9.4.0" - esquery "^1.4.0" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" @@ -3926,10 +3932,10 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.2.tgz#c6d3fee05dd665808e2ad870631f221f5617b1d1" + integrity sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng== dependencies: estraverse "^5.1.0" diff --git a/packages/dart_frog_cli/CHANGELOG.md b/packages/dart_frog_cli/CHANGELOG.md index b9daabcda..a31862ecc 100644 --- a/packages/dart_frog_cli/CHANGELOG.md +++ b/packages/dart_frog_cli/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.3.3 + +- fix: adjust `InternetAddress` to `anyIPv6` +- refactor: update to Dart 2.19 and `very_good_analysis ^4.0.0` + # 0.3.2 - fix: update silently fails when the sdk is incompatible diff --git a/packages/dart_frog_cli/lib/src/commands/dev/dev.dart b/packages/dart_frog_cli/lib/src/commands/dev/dev.dart index 33217e161..f9a957e00 100644 --- a/packages/dart_frog_cli/lib/src/commands/dev/dev.dart +++ b/packages/dart_frog_cli/lib/src/commands/dev/dev.dart @@ -1,7 +1,6 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io' as io; -import 'dart:io'; import 'package:dart_frog_cli/src/command.dart'; import 'package:dart_frog_cli/src/commands/commands.dart'; @@ -54,7 +53,7 @@ class DevCommand extends DartFrogCommand { /// {@macro dev_command} DevCommand({ super.logger, - void Function(Directory)? ensureRuntimeCompatibility, + void Function(io.Directory)? ensureRuntimeCompatibility, DirectoryWatcherBuilder? directoryWatcher, GeneratorBuilder? generator, RestorableDirectoryGeneratorTargetBuilder? generatorTarget, @@ -81,7 +80,7 @@ class DevCommand extends DartFrogCommand { ); } - final void Function(Directory) _ensureRuntimeCompatibility; + final void Function(io.Directory) _ensureRuntimeCompatibility; final DirectoryWatcherBuilder _directoryWatcher; final GeneratorBuilder _generator; final Exit _exit; diff --git a/packages/dart_frog_cli/lib/src/version.dart b/packages/dart_frog_cli/lib/src/version.dart index 246b602e6..33ca3b7aa 100644 --- a/packages/dart_frog_cli/lib/src/version.dart +++ b/packages/dart_frog_cli/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '0.3.2'; +const packageVersion = '0.3.3'; diff --git a/packages/dart_frog_cli/pubspec.yaml b/packages/dart_frog_cli/pubspec.yaml index 291b4d75f..33ddc63f6 100644 --- a/packages/dart_frog_cli/pubspec.yaml +++ b/packages/dart_frog_cli/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_frog_cli description: The official command line interface for Dart Frog. Built by Very Good Ventures. -version: 0.3.2 +version: 0.3.3 homepage: https://dartfrog.vgv.dev repository: https://github.com/VeryGoodOpenSource/dart_frog issue_tracker: https://github.com/VeryGoodOpenSource/dart_frog/issues @@ -11,7 +11,7 @@ environment: dependencies: args: ^2.1.0 - cli_completion: ^0.2.0 + cli_completion: ^0.3.0 mason: ^0.1.0-dev.39 meta: ^1.7.0 path: ^1.8.1 diff --git a/packages/dart_frog_lint/.gitignore b/packages/dart_frog_lint/.gitignore new file mode 100644 index 000000000..cc063e820 --- /dev/null +++ b/packages/dart_frog_lint/.gitignore @@ -0,0 +1,10 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock + +# Test related files +coverage/ \ No newline at end of file diff --git a/packages/dart_frog_lint/CHANGELOG.md b/packages/dart_frog_lint/CHANGELOG.md new file mode 100644 index 000000000..93fbc2511 --- /dev/null +++ b/packages/dart_frog_lint/CHANGELOG.md @@ -0,0 +1,3 @@ +# Unreleased + +Initial release \ No newline at end of file diff --git a/packages/dart_frog_lint/LICENSE b/packages/dart_frog_lint/LICENSE new file mode 100644 index 000000000..7918ffb58 --- /dev/null +++ b/packages/dart_frog_lint/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Very Good Ventures + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/dart_frog_lint/README.md b/packages/dart_frog_lint/README.md new file mode 100644 index 000000000..1fc1a99fb --- /dev/null +++ b/packages/dart_frog_lint/README.md @@ -0,0 +1,230 @@ +Dart_frog_lint is a developer tool for users of dart_frog, to help spot commong mistakes. +It helps verify that file conventions are properly respected. + +## Table of content + +- [Table of content](#table-of-content) +- [Installing dart\_frog\_lint](#installing-dart_frog_lint) +- [Enabling/disabling lints.](#enablingdisabling-lints) + - [Disable one specific rule](#disable-one-specific-rule) + - [Disable all lints by default](#disable-all-lints-by-default) +- [Running dart\_frog\_lint in the terminal/CI](#running-dart_frog_lint-in-the-terminalci) +- [All the lints](#all-the-lints) + - [dart\_frog\_entrypoint](#dart_frog_entrypoint) + - [dart\_frog\_middleware](#dart_frog_middleware) + - [dart\_frog\_route](#dart_frog_route) + +## Installing dart_frog_lint + +Dart_frog_lint is implemented using [custom_lint]. As such, it uses custom_lint's installation logic. +Long story short: + +- Add both dart_frog_lint and custom_lint to your `pubspec.yaml`: + ```yaml + dev_dependencies: + custom_lint: + dart_frog_lint: + ``` +- Enable `custom_lint`'s plugin in your `analysis_options.yaml`: + + ```yaml + analyzer: + plugins: + - custom_lint + ``` + +## Enabling/disabling lints. + +By default when installing dart_frog_lint, most of the lints will be enabled. +To change this, you have a few options. + +### Disable one specific rule + +You may dislike one of the various lint rules offered by dart_frog_lint. +In that event, you can explicitly disable this lint rule for your project +by modifying the `analysis_options.yaml` + +```yaml +analyzer: + plugins: + - custom_lint + +custom_lint: + rules: + # Explicitly disable one lint rule + - dart_frog_request: false +``` + +Note that you can both enable and disable lint rules at once. +This can be useful if your `analysis_options.yaml` includes another one: + +```yaml +include: path/to/another/analys_options.yaml +analyzer: + plugins: + - custom_lint + +custom_lint: + rules: + # Enable one rule + - dart_frog_middleware + # Disable another + - dart_frog_request: false +``` + +### Disable all lints by default + +Instead of having all lints on by default and manually disabling lints of your choice, +you can switch to the opposite logic: +Have lints off by default, and manually enable lints. + +This can be done in your `analysis_options.yaml` with the following: + +```yaml +analyzer: + plugins: + - custom_lint + +custom_lint: + # Forcibly disable lint rules by default + enable_all_lint_rules: false + rules: + # You can now enable one specific rule in the "rules" list + - missing_provider_scope +``` + +## Running dart_frog_lint in the terminal/CI + +Custom lint rules created by dart_frog_lint may not show-up in `dart analyze`. +To fix this, you can run a custom command line: `custom_lint`. + +Since your project should already have custom_lint installed +(cf [installing dart_frog_lint](#installing-dart_frog_lint)), then you should be +able to run: + +```sh +dart run custom_lint +``` + +Alternatively, you can globally install `custom_lint`: + +```sh +# Install custom_lint for all projects +dart pub global activate custom_lint +# run custom_lint's command line in a project +custom_lint +``` + +## All the lints + +### dart_frog_entrypoint + +The `dart_frog_entrypoint` lint checks that `main.dart` files contain a +valid `run` function. See also [Creating a custom entrypoint](https://dartfrog.vgv.dev/docs/advanced/custom_entrypoint). + +**Good**: + +```dart +// main.dart +Future run(Handler handler, InternetAddress ip, int port) { + // TODO +} +``` + +**Bad**: + +```dart +// An empty main.dart file +// The file must contain a top-level function named "run" +``` + +```dart +// main.dart + +// A "run" function is present, but the return value or parameters are incorrrect +void run() {} +``` + +### dart_frog_middleware + +The `dart_frog_middleware` lint checks that `routes/*_middleware.dart` files contain a +valid `middleware` function. See also [Middlewares](https://dartfrog.vgv.dev/docs/basics/middleware). + +**Good**: + +```dart +// routes/my_middleware.dart +Handler middleware(Handler handler) { + return (context) async { + // TODO + }; +} +``` + +**Bad**: + +```dart +// routes/my_middleware.dart +// The file must contain a valid top-level "middleware" function +``` + +```dart +// routes/my_middleware.dart +// The file must contain a valid top-level "middleware" function + +// A "middleware" function is present, but the return value or parameters are incorrrect +void middleware() {} +``` + +### dart_frog_route + +The `dart_frog_route` lint checks that `routes/*.dart` files contain a +valid `onRequest` function. See also [Routes](https://dartfrog.vgv.dev/docs/basics/routes). + +**Good**: + +```dart +// routes/hello.dart +Response onRequest(RequestContext context) { + // TODO +} +``` + +```dart +// routes/posts/[id].dart +// Dynamic routes are supported too +Response onRequest(RequestContext context, String id) { + // TODO +} +``` + +```dart +// routes/example.dart +// Routes can return a Future +Future onRequest(RequestContext context) async { + // TODO +} +``` + +**Bad**: + +```dart +// routes/hello.dart +// The file must contain a valid top-level "onRequest" function +``` + +```dart +// routes/hello.dart +// An "onRequest" function is present, but the return value or parameters are incorrrect +void onRequest() {} +``` + +```dart +// routes/posts/[id].dart +// The route is a dynamic route, but onRequest doesn't receive the correct number of parameters. +Response onRequest(RequestContext context) { + // TODO +} +``` + +[custom_lint]: https://pub.dev/packages/custom_lint diff --git a/packages/dart_frog_lint/analysis_options.yaml b/packages/dart_frog_lint/analysis_options.yaml new file mode 100644 index 000000000..84e34fba4 --- /dev/null +++ b/packages/dart_frog_lint/analysis_options.yaml @@ -0,0 +1 @@ +include: package:very_good_analysis/analysis_options.4.0.0.yaml diff --git a/packages/dart_frog_lint/coverage_badge.svg b/packages/dart_frog_lint/coverage_badge.svg new file mode 100644 index 000000000..499e98ce2 --- /dev/null +++ b/packages/dart_frog_lint/coverage_badge.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + coverage + coverage + 100% + 100% + + diff --git a/packages/dart_frog_lint/example/.gitignore b/packages/dart_frog_lint/example/.gitignore new file mode 100644 index 000000000..965f204b9 --- /dev/null +++ b/packages/dart_frog_lint/example/.gitignore @@ -0,0 +1 @@ +custom_lint.log \ No newline at end of file diff --git a/packages/dart_frog_lint/example/analysis_options.yaml b/packages/dart_frog_lint/example/analysis_options.yaml new file mode 100644 index 000000000..ff7873384 --- /dev/null +++ b/packages/dart_frog_lint/example/analysis_options.yaml @@ -0,0 +1,7 @@ +include: ../analysis_options.yaml +analyzer: + plugins: + - custom_lint +linter: + rules: + file_names: false diff --git a/packages/dart_frog_lint/example/lib/out.dart b/packages/dart_frog_lint/example/lib/out.dart new file mode 100644 index 000000000..e69de29bb diff --git a/packages/dart_frog_lint/example/main.dart b/packages/dart_frog_lint/example/main.dart new file mode 100644 index 000000000..7355f128e --- /dev/null +++ b/packages/dart_frog_lint/example/main.dart @@ -0,0 +1,7 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +Future run(Handler handler, InternetAddress ip, int port) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/example/pubspec.yaml b/packages/dart_frog_lint/example/pubspec.yaml new file mode 100644 index 000000000..0589407cd --- /dev/null +++ b/packages/dart_frog_lint/example/pubspec.yaml @@ -0,0 +1,15 @@ +name: dart_frog_lint_example +version: 0.0.1 +publish_to: "none" + +environment: + sdk: ">=2.17.0 <3.0.0" + +dependencies: + dart_frog: + path: ../../dart_frog + +dev_dependencies: + dart_frog_lint: + path: .. + very_good_analysis: ^4.0.0 diff --git a/packages/dart_frog_lint/example/routes/async_response.dart b/packages/dart_frog_lint/example/routes/async_response.dart new file mode 100644 index 000000000..68c97fccd --- /dev/null +++ b/packages/dart_frog_lint/example/routes/async_response.dart @@ -0,0 +1,5 @@ +import 'package:dart_frog/dart_frog.dart'; + +Future onRequest(RequestContext context) async { + throw UnimplementedError(''); +} diff --git a/packages/dart_frog_lint/example/routes/invalid_context.dart b/packages/dart_frog_lint/example/routes/invalid_context.dart new file mode 100644 index 000000000..9656ee8b8 --- /dev/null +++ b/packages/dart_frog_lint/example/routes/invalid_context.dart @@ -0,0 +1,6 @@ +import 'package:dart_frog/dart_frog.dart'; + +// expect_lint: dart_frog_route +Response onRequest(String context) { + return Response(); +} diff --git a/packages/dart_frog_lint/example/routes/invalid_parameter_middleware.dart b/packages/dart_frog_lint/example/routes/invalid_parameter_middleware.dart new file mode 100644 index 000000000..9d56fd5b3 --- /dev/null +++ b/packages/dart_frog_lint/example/routes/invalid_parameter_middleware.dart @@ -0,0 +1,6 @@ +import 'package:dart_frog/dart_frog.dart'; + +// expect_lint: dart_frog_middleware +Handler middleware(int handler) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/example/routes/invalid_response.dart b/packages/dart_frog_lint/example/routes/invalid_response.dart new file mode 100644 index 000000000..fbf5e8d6c --- /dev/null +++ b/packages/dart_frog_lint/example/routes/invalid_response.dart @@ -0,0 +1,6 @@ +import 'package:dart_frog/dart_frog.dart'; + +// expect_lint: dart_frog_route +String onRequest(RequestContext context) { + return ''; +} diff --git a/packages/dart_frog_lint/example/routes/invalid_return_middleware.dart b/packages/dart_frog_lint/example/routes/invalid_return_middleware.dart new file mode 100644 index 000000000..66d8d02be --- /dev/null +++ b/packages/dart_frog_lint/example/routes/invalid_return_middleware.dart @@ -0,0 +1,6 @@ +import 'package:dart_frog/dart_frog.dart'; + +// expect_lint: dart_frog_middleware +String middleware(Handler handler) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/example/routes/missing_middleware.dart b/packages/dart_frog_lint/example/routes/missing_middleware.dart new file mode 100644 index 000000000..f01ce832f --- /dev/null +++ b/packages/dart_frog_lint/example/routes/missing_middleware.dart @@ -0,0 +1,4 @@ +// expect_lint: dart_frog_middleware +import 'dart:async'; + +FutureOr fn() {} diff --git a/packages/dart_frog_lint/example/routes/missing_on_request.dart b/packages/dart_frog_lint/example/routes/missing_on_request.dart new file mode 100644 index 000000000..9768e88db --- /dev/null +++ b/packages/dart_frog_lint/example/routes/missing_on_request.dart @@ -0,0 +1,4 @@ +// expect_lint: dart_frog_route +import 'dart:async'; + +FutureOr fn() {} diff --git a/packages/dart_frog_lint/example/routes/no_context.dart b/packages/dart_frog_lint/example/routes/no_context.dart new file mode 100644 index 000000000..ec041209d --- /dev/null +++ b/packages/dart_frog_lint/example/routes/no_context.dart @@ -0,0 +1,6 @@ +import 'package:dart_frog/dart_frog.dart'; + +// expect_lint: dart_frog_route +Response onRequest() { + return Response(); +} diff --git a/packages/dart_frog_lint/example/routes/no_parameter_middleware.dart b/packages/dart_frog_lint/example/routes/no_parameter_middleware.dart new file mode 100644 index 000000000..636a884f1 --- /dev/null +++ b/packages/dart_frog_lint/example/routes/no_parameter_middleware.dart @@ -0,0 +1,6 @@ +import 'package:dart_frog/dart_frog.dart'; + +// expect_lint: dart_frog_middleware +Handler middleware() { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/example/routes/parametrized_route/[userId2].dart b/packages/dart_frog_lint/example/routes/parametrized_route/[userId2].dart new file mode 100644 index 000000000..e66011d9f --- /dev/null +++ b/packages/dart_frog_lint/example/routes/parametrized_route/[userId2].dart @@ -0,0 +1,7 @@ +import 'package:dart_frog/dart_frog.dart'; + +// Incorrect parameter type +// expect_lint: dart_frog_route +Response onRequest(RequestContext context, int userId2) { + return Response(); +} diff --git a/packages/dart_frog_lint/example/routes/parametrized_route/[userId].dart b/packages/dart_frog_lint/example/routes/parametrized_route/[userId].dart new file mode 100644 index 000000000..6a7f6fbc3 --- /dev/null +++ b/packages/dart_frog_lint/example/routes/parametrized_route/[userId].dart @@ -0,0 +1,7 @@ +import 'package:dart_frog/dart_frog.dart'; + +// Missing parameter +// expect_lint: dart_frog_route +Response onRequest(RequestContext context) { + return Response(); +} diff --git a/packages/dart_frog_lint/example/routes/sync_response.dart b/packages/dart_frog_lint/example/routes/sync_response.dart new file mode 100644 index 000000000..822f62a84 --- /dev/null +++ b/packages/dart_frog_lint/example/routes/sync_response.dart @@ -0,0 +1,5 @@ +import 'package:dart_frog/dart_frog.dart'; + +Response onRequest(RequestContext context) { + throw UnimplementedError(''); +} diff --git a/packages/dart_frog_lint/example/routes/valid_middleware.dart b/packages/dart_frog_lint/example/routes/valid_middleware.dart new file mode 100644 index 000000000..1b8fa2adc --- /dev/null +++ b/packages/dart_frog_lint/example/routes/valid_middleware.dart @@ -0,0 +1,5 @@ +import 'package:dart_frog/dart_frog.dart'; + +Handler middleware(Handler handler) { + return handler; +} diff --git a/packages/dart_frog_lint/lib/dart_frog_lint.dart b/packages/dart_frog_lint/lib/dart_frog_lint.dart new file mode 100644 index 000000000..63635c6d8 --- /dev/null +++ b/packages/dart_frog_lint/lib/dart_frog_lint.dart @@ -0,0 +1,16 @@ +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:dart_frog_lint/src/dart_frog_entrypoint.dart'; +import 'package:dart_frog_lint/src/dart_frog_middleware.dart'; +import 'package:dart_frog_lint/src/dart_frog_route.dart'; + +/// The entrypoint of dart_frog_lint +PluginBase createPlugin() => _DartFrogLintPlugin(); + +class _DartFrogLintPlugin extends PluginBase { + @override + List getLintRules(CustomLintConfigs configs) => [ + const DartFrogRequest(), + const DartFrogMiddleware(), + const DartFrogEntrypoint(), + ]; +} diff --git a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart new file mode 100644 index 000000000..5e1978037 --- /dev/null +++ b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart @@ -0,0 +1,90 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:collection/collection.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:dart_frog_lint/src/types.dart'; + +/// {@template dart_frog_lint.request} +/// The definition of `dart_frog_entrypoint` lints. +/// {@endtemplate} +class DartFrogEntrypoint extends DartLintRule { + /// {@macro dart_frog_lint.request} + const DartFrogEntrypoint() + : super( + code: const LintCode( + name: 'dart_frog_entrypoint', + problemMessage: 'Main files should define a valid "run" function.', + ), + ); + + @override + List get filesToAnalyze => ['main.dart']; + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addCompilationUnit((node) { + // Search for a function declaration with the name "run" + final run = + node.declarations.whereType().firstWhereOrNull( + (declaration) => declaration.name.lexeme == 'run', + ); + + if (run == null) { + // No function declaration found with the name "run" + reporter.reportErrorForNode(code, node.directives.firstOrNull ?? node); + return; + } + + if (run.functionExpression.parameters?.parameters.length != 3) { + // Only one parameter is allowed + reporter.reportErrorForNode(code, run); + return; + } + + final handlerType = run.functionExpression.parameters?.parameters + .firstOrNull?.declaredElement?.type; + final ipType = run.functionExpression.parameters?.parameters + .elementAtOrNull(1) + ?.declaredElement + ?.type; + final portType = run.functionExpression.parameters?.parameters + .elementAtOrNull(2) + ?.declaredElement + ?.type; + + if (handlerType == null || !isHandler(handlerType)) { + // The parameter is not a Handler + reporter.reportErrorForNode(code, run); + return; + } + + if (ipType == null || !internetAddressTypeChecker.isExactlyType(ipType)) { + // The parameter is not a Handler + reporter.reportErrorForNode(code, run); + return; + } + + if (portType?.isDartCoreInt != true) { + // The parameter is not a Handler + reporter.reportErrorForNode(code, run); + return; + } + + final returnType = run.returnType?.type; + if (returnType == null || + !returnType.isDartAsyncFuture || + !httpServerTypeChecker.isExactlyType( + (returnType as InterfaceType).typeArguments.single, + )) { + // The parameter is not a Handler + reporter.reportErrorForNode(code, run); + return; + } + }); + } +} diff --git a/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart b/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart new file mode 100644 index 000000000..d6e84f72f --- /dev/null +++ b/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart @@ -0,0 +1,65 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:collection/collection.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:dart_frog_lint/src/types.dart'; + +/// {@template dart_frog_lint.request} +/// The definition of `dart_frog_request` lints. +/// {@endtemplate} +class DartFrogMiddleware extends DartLintRule { + /// {@macro dart_frog_lint.request} + const DartFrogMiddleware() + : super( + code: const LintCode( + name: 'dart_frog_middleware', + problemMessage: + 'Middleware files should define a valid "middleware" function.', + ), + ); + + @override + List get filesToAnalyze => ['routes/**_middleware.dart']; + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addCompilationUnit((node) { + // Search for a function declaration with the name "middleware" + final middleware = + node.declarations.whereType().firstWhereOrNull( + (declaration) => declaration.name.lexeme == 'middleware', + ); + + if (middleware == null) { + // No function declaration found with the name "middleware" + reporter.reportErrorForNode(code, node.directives.firstOrNull ?? node); + return; + } + + if (middleware.functionExpression.parameters?.parameters.length != 1) { + // Only one parameter is allowed + reporter.reportErrorForNode(code, middleware); + return; + } + + final handlerType = middleware.functionExpression.parameters?.parameters + .firstOrNull?.declaredElement?.type; + if (handlerType == null || !isHandler(handlerType)) { + // The parameter is not a Handler + reporter.reportErrorForNode(code, middleware); + return; + } + + final returnType = middleware.returnType?.type; + if (returnType == null || !isHandler(returnType)) { + // The parameter is not a Handler + reporter.reportErrorForNode(code, middleware); + return; + } + }); + } +} diff --git a/packages/dart_frog_lint/lib/src/dart_frog_route.dart b/packages/dart_frog_lint/lib/src/dart_frog_route.dart new file mode 100644 index 000000000..0d1327e42 --- /dev/null +++ b/packages/dart_frog_lint/lib/src/dart_frog_route.dart @@ -0,0 +1,89 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:collection/collection.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:dart_frog_lint/src/parse_route.dart'; +import 'package:dart_frog_lint/src/types.dart'; + +/// {@template dart_frog_lint.request} +/// The definition of `dart_frog_request` lints. +/// {@endtemplate} +class DartFrogRequest extends DartLintRule { + /// {@macro dart_frog_lint.request} + const DartFrogRequest() + : super( + code: const LintCode( + name: 'dart_frog_route', + problemMessage: + 'Dart files within the "route" directory should define a ' + 'valid "onRequest" function.', + ), + ); + + @override + List get filesToAnalyze => ['routes/**.dart']; + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + // package:glob which filesToAnalyze uses does not seem to support exclude + // patterns, so we have to manually filter out the _middleware.dart files + if (resolver.path.endsWith('_middleware.dart')) return; + + context.registry.addCompilationUnit((node) { + // Search for a function declaration with the name "onRequest" + final onRequest = + node.declarations.whereType().firstWhereOrNull( + (declaration) => declaration.name.lexeme == 'onRequest', + ); + + if (onRequest == null) { + // No function declaration found with the name "onRequest" + reporter.reportErrorForNode(code, node.directives.firstOrNull ?? node); + return; + } + + final parameters = onRequest.functionExpression.parameters; + if (parameters == null) { + // Possible syntax error + reporter.reportErrorForNode(code, onRequest); + return; + } + + final contextParameterType = onRequest.functionExpression.parameters + ?.parameters.firstOrNull?.declaredElement?.type; + if (contextParameterType == null || + !requestContextTypeChecker.isExactlyType(contextParameterType)) { + // The onRequest function doesn't have a "RequestContext" parameter + reporter.reportErrorForNode(code, onRequest); + return; + } + + if (!isOnRequestResponse(onRequest.returnType?.type)) { + // The onRequest function doesn't return a "Response" + reporter.reportErrorForNode(code, onRequest); + return; + } + + final route = parseRoute(resolver.path); + if (onRequest.functionExpression.parameters?.parameters.length != + 1 + route.parameters.length) { + // The onRequest function doesn't have the correct number of parameters + reporter.reportErrorForNode(code, onRequest); + return; + } + + for (final parameter in parameters.parameters.skip(1)) { + final parameterType = parameter.declaredElement?.type; + if (parameterType?.isDartCoreString != true) { + // Route parameters should be positional strings + reporter.reportErrorForNode(code, onRequest); + return; + } + } + }); + } +} diff --git a/packages/dart_frog_lint/lib/src/parse_route.dart b/packages/dart_frog_lint/lib/src/parse_route.dart new file mode 100644 index 000000000..949d62c79 --- /dev/null +++ b/packages/dart_frog_lint/lib/src/parse_route.dart @@ -0,0 +1,43 @@ +import 'package:path/path.dart' as p; + +/// A parsed route file. +class RouteFile { + RouteFile._(this.path, this.parameters); + + /// The route path relative to the "routes" directory. + final String path; + + /// The parameter names in the route path. + final List parameters; +} + +/// Decode a route path into a [RouteFile]. +RouteFile parseRoute(String path) { + final routePath = _findEnclosingRouteDirectory(path); + if (routePath == null) { + throw ArgumentError.value( + path, + 'path', + 'The path must be within a "routes" directory.', + ); + } + + final relativePath = p.relative(path, from: routePath); + final split = p.split(relativePath); + final parameters = split + .map(p.basenameWithoutExtension) + .where((element) => element.startsWith('[') && element.endsWith(']')) + .map((e) => e.substring(1, e.length - 1)) + .toList(); + + return RouteFile._(relativePath, parameters); +} + +/// Find the enclosing "routes" directory of a route path. +String? _findEnclosingRouteDirectory(String path) { + final split = p.split(path); + final routeIndex = split.lastIndexOf('routes'); + if (routeIndex == -1) return null; + + return p.joinAll(split.sublist(0, routeIndex)); +} diff --git a/packages/dart_frog_lint/lib/src/types.dart b/packages/dart_frog_lint/lib/src/types.dart new file mode 100644 index 000000000..498f1fed8 --- /dev/null +++ b/packages/dart_frog_lint/lib/src/types.dart @@ -0,0 +1,46 @@ +import 'package:analyzer/dart/element/type.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +/// [TypeChecker] for `RequestContext` +const requestContextTypeChecker = TypeChecker.fromName( + 'RequestContext', + packageName: 'dart_frog', +); + +/// [TypeChecker] for `Response` +const _responseTypeChecker = TypeChecker.fromName( + 'Response', + packageName: 'dart_frog', +); + +/// Checks that [type] is `Response | Future`. +bool isOnRequestResponse(DartType? type) { + if (type == null) return false; + if (_responseTypeChecker.isExactlyType(type)) return true; + + if (!type.isDartAsyncFuture) return false; + + type as InterfaceType; + return _responseTypeChecker.isExactlyType(type.typeArguments.first); +} + +/// [TypeChecker] for `Handler` +const _handlerTypeChecker = TypeChecker.fromName( + 'Handler', + packageName: 'dart_frog', +); + +/// Checks that a type is assignable with `Handler`. +/// +/// Since `Handler` is a typedef, we need check the alias instead of type matchs +bool isHandler(DartType type) { + final alias = type.alias; + if (alias == null) return false; + return _handlerTypeChecker.isExactly(alias.element); +} + +/// [TypeChecker] for `InternetAddress` +const internetAddressTypeChecker = TypeChecker.fromName('InternetAddress'); + +/// [TypeChecker] for `HttpServer` +const httpServerTypeChecker = TypeChecker.fromName('HttpServer'); diff --git a/packages/dart_frog_lint/pubspec.yaml b/packages/dart_frog_lint/pubspec.yaml new file mode 100644 index 000000000..878a9681a --- /dev/null +++ b/packages/dart_frog_lint/pubspec.yaml @@ -0,0 +1,23 @@ +name: dart_frog_lint +description: Lint rules and refactorings for dart_frog +version: 0.1.0 +homepage: https://dartfrog.vgv.dev +repository: https://github.com/VeryGoodOpenSource/dart_frog +issue_tracker: https://github.com/VeryGoodOpenSource/dart_frog/issues +documentation: https://dartfrog.vgv.dev/docs/overview + +environment: + sdk: ">=2.19.0 <3.0.0" + +dependencies: + analyzer: ^5.2.0 + analyzer_plugin: ^0.11.2 + collection: ^1.17.1 + custom_lint_builder: ^0.2.10 + glob: ^2.1.1 + meta: ^1.9.0 + path: ^1.8.3 + +dev_dependencies: + test: ^1.21.1 + very_good_analysis: ^4.0.0 From b0b95da5068fc90d54c9020a923fc517ea86d9cb Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 7 Mar 2023 15:58:13 +0100 Subject: [PATCH 02/16] Spell check --- packages/dart_frog_lint/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/dart_frog_lint/README.md b/packages/dart_frog_lint/README.md index 1fc1a99fb..ea70e3765 100644 --- a/packages/dart_frog_lint/README.md +++ b/packages/dart_frog_lint/README.md @@ -1,4 +1,4 @@ -Dart_frog_lint is a developer tool for users of dart_frog, to help spot commong mistakes. +Dart_frog_lint is a developer tool for users of dart_frog, to help spot common mistakes. It helps verify that file conventions are properly respected. ## Table of content @@ -59,7 +59,7 @@ Note that you can both enable and disable lint rules at once. This can be useful if your `analysis_options.yaml` includes another one: ```yaml -include: path/to/another/analys_options.yaml +include: path/to/another/analysis_options.yaml analyzer: plugins: - custom_lint @@ -141,14 +141,14 @@ Future run(Handler handler, InternetAddress ip, int port) { ```dart // main.dart -// A "run" function is present, but the return value or parameters are incorrrect +// A "run" function is present, but the return value or parameters are incorrect void run() {} ``` ### dart_frog_middleware The `dart_frog_middleware` lint checks that `routes/*_middleware.dart` files contain a -valid `middleware` function. See also [Middlewares](https://dartfrog.vgv.dev/docs/basics/middleware). +valid `middleware` function. See also [Middleware](https://dartfrog.vgv.dev/docs/basics/middleware). **Good**: @@ -172,7 +172,7 @@ Handler middleware(Handler handler) { // routes/my_middleware.dart // The file must contain a valid top-level "middleware" function -// A "middleware" function is present, but the return value or parameters are incorrrect +// A "middleware" function is present, but the return value or parameters are incorrect void middleware() {} ``` @@ -215,7 +215,7 @@ Future onRequest(RequestContext context) async { ```dart // routes/hello.dart -// An "onRequest" function is present, but the return value or parameters are incorrrect +// An "onRequest" function is present, but the return value or parameters are incorrect void onRequest() {} ``` From f7a066c6450ea844a6e38a7bbdd63621c34b01fc Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Mon, 13 Mar 2023 17:58:30 +0100 Subject: [PATCH 03/16] Update packages/dart_frog_lint/README.md Co-authored-by: Mike Diarmid --- packages/dart_frog_lint/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/README.md b/packages/dart_frog_lint/README.md index ea70e3765..85275102d 100644 --- a/packages/dart_frog_lint/README.md +++ b/packages/dart_frog_lint/README.md @@ -90,7 +90,7 @@ custom_lint: enable_all_lint_rules: false rules: # You can now enable one specific rule in the "rules" list - - missing_provider_scope + - dart_frog_middleware ``` ## Running dart_frog_lint in the terminal/CI From 6e6b78df068396aa487a9d0ccafe47c7e6bca6c2 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 15 Mar 2023 11:05:07 +0100 Subject: [PATCH 04/16] Add docs comment --- packages/dart_frog_lint/example/lib/out.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dart_frog_lint/example/lib/out.dart b/packages/dart_frog_lint/example/lib/out.dart index e69de29bb..61d0cd9da 100644 --- a/packages/dart_frog_lint/example/lib/out.dart +++ b/packages/dart_frog_lint/example/lib/out.dart @@ -0,0 +1 @@ +// An empty file for checking that routes lints don't trigger on lib folders From b85d39f6d18a25248d6e930aac0db82613368352 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Mon, 3 Apr 2023 23:46:06 +0200 Subject: [PATCH 05/16] Reorganize example --- .../{ => dart_frog_middleware}/invalid_parameter_middleware.dart | 0 .../{ => dart_frog_middleware}/invalid_return_middleware.dart | 0 .../routes/{ => dart_frog_middleware}/missing_middleware.dart | 0 .../{ => dart_frog_middleware}/no_parameter_middleware.dart | 0 .../routes/{ => dart_frog_middleware}/valid_middleware.dart | 0 .../example/routes/{ => dart_frog_route}/async_response.dart | 0 .../example/routes/{ => dart_frog_route}/invalid_context.dart | 0 .../example/routes/{ => dart_frog_route}/invalid_response.dart | 0 .../example/routes/{ => dart_frog_route}/missing_on_request.dart | 0 .../example/routes/{ => dart_frog_route}/no_context.dart | 0 .../{ => dart_frog_route}/parametrized_route/[userId2].dart | 0 .../routes/{ => dart_frog_route}/parametrized_route/[userId].dart | 0 .../example/routes/{ => dart_frog_route}/sync_response.dart | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename packages/dart_frog_lint/example/routes/{ => dart_frog_middleware}/invalid_parameter_middleware.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_middleware}/invalid_return_middleware.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_middleware}/missing_middleware.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_middleware}/no_parameter_middleware.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_middleware}/valid_middleware.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/async_response.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/invalid_context.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/invalid_response.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/missing_on_request.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/no_context.dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/parametrized_route/[userId2].dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/parametrized_route/[userId].dart (100%) rename packages/dart_frog_lint/example/routes/{ => dart_frog_route}/sync_response.dart (100%) diff --git a/packages/dart_frog_lint/example/routes/invalid_parameter_middleware.dart b/packages/dart_frog_lint/example/routes/dart_frog_middleware/invalid_parameter_middleware.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/invalid_parameter_middleware.dart rename to packages/dart_frog_lint/example/routes/dart_frog_middleware/invalid_parameter_middleware.dart diff --git a/packages/dart_frog_lint/example/routes/invalid_return_middleware.dart b/packages/dart_frog_lint/example/routes/dart_frog_middleware/invalid_return_middleware.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/invalid_return_middleware.dart rename to packages/dart_frog_lint/example/routes/dart_frog_middleware/invalid_return_middleware.dart diff --git a/packages/dart_frog_lint/example/routes/missing_middleware.dart b/packages/dart_frog_lint/example/routes/dart_frog_middleware/missing_middleware.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/missing_middleware.dart rename to packages/dart_frog_lint/example/routes/dart_frog_middleware/missing_middleware.dart diff --git a/packages/dart_frog_lint/example/routes/no_parameter_middleware.dart b/packages/dart_frog_lint/example/routes/dart_frog_middleware/no_parameter_middleware.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/no_parameter_middleware.dart rename to packages/dart_frog_lint/example/routes/dart_frog_middleware/no_parameter_middleware.dart diff --git a/packages/dart_frog_lint/example/routes/valid_middleware.dart b/packages/dart_frog_lint/example/routes/dart_frog_middleware/valid_middleware.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/valid_middleware.dart rename to packages/dart_frog_lint/example/routes/dart_frog_middleware/valid_middleware.dart diff --git a/packages/dart_frog_lint/example/routes/async_response.dart b/packages/dart_frog_lint/example/routes/dart_frog_route/async_response.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/async_response.dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/async_response.dart diff --git a/packages/dart_frog_lint/example/routes/invalid_context.dart b/packages/dart_frog_lint/example/routes/dart_frog_route/invalid_context.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/invalid_context.dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/invalid_context.dart diff --git a/packages/dart_frog_lint/example/routes/invalid_response.dart b/packages/dart_frog_lint/example/routes/dart_frog_route/invalid_response.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/invalid_response.dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/invalid_response.dart diff --git a/packages/dart_frog_lint/example/routes/missing_on_request.dart b/packages/dart_frog_lint/example/routes/dart_frog_route/missing_on_request.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/missing_on_request.dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/missing_on_request.dart diff --git a/packages/dart_frog_lint/example/routes/no_context.dart b/packages/dart_frog_lint/example/routes/dart_frog_route/no_context.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/no_context.dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/no_context.dart diff --git a/packages/dart_frog_lint/example/routes/parametrized_route/[userId2].dart b/packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId2].dart similarity index 100% rename from packages/dart_frog_lint/example/routes/parametrized_route/[userId2].dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId2].dart diff --git a/packages/dart_frog_lint/example/routes/parametrized_route/[userId].dart b/packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId].dart similarity index 100% rename from packages/dart_frog_lint/example/routes/parametrized_route/[userId].dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId].dart diff --git a/packages/dart_frog_lint/example/routes/sync_response.dart b/packages/dart_frog_lint/example/routes/dart_frog_route/sync_response.dart similarity index 100% rename from packages/dart_frog_lint/example/routes/sync_response.dart rename to packages/dart_frog_lint/example/routes/dart_frog_route/sync_response.dart From 8ed36fae606413fb04689461818f4c849b67224c Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 4 Apr 2023 00:03:14 +0200 Subject: [PATCH 06/16] Add parseRoute test --- .../test/src/parse_route_test.dart | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 packages/dart_frog_lint/test/src/parse_route_test.dart diff --git a/packages/dart_frog_lint/test/src/parse_route_test.dart b/packages/dart_frog_lint/test/src/parse_route_test.dart new file mode 100644 index 000000000..eae1ffc2e --- /dev/null +++ b/packages/dart_frog_lint/test/src/parse_route_test.dart @@ -0,0 +1,36 @@ +import 'package:dart_frog_lint/src/parse_route.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + group(parseRoute, () { + test('throws if path is not within a "routes" directory', () { + final path = p.join('path', 'to', 'file.dart'); + expect(() => parseRoute(path), throwsArgumentError); + }); + + test('parses a route path', () { + final path = p.join('routes', 'path', 'to', 'file.dart'); + final route = parseRoute(path); + + expect(route.path, 'routes/path/to/file.dart'); + expect(route.parameters, isEmpty); + }); + + test('parses a route with parameters', () { + final path = p.join('routes', '[path]', 'to', '[file].dart'); + final route = parseRoute(path); + + expect(route.path, 'routes/[path]/to/[file].dart'); + expect(route.parameters, ['path', 'file']); + }); + + test('[] must be placed around the entire parameter name', () { + final path = p.join('routes', 'p[ath]', 'to', '[fil]e.dart'); + final route = parseRoute(path); + + expect(route.path, 'routes/p[ath]/to/[fil]e.dart'); + expect(route.parameters, isEmpty); + }); + }); +} From 6dd53eb2684b36e1963c36bb3dc222f074ebeb03 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 4 Apr 2023 14:01:45 +0200 Subject: [PATCH 07/16] Update packages/dart_frog_lint/lib/src/dart_frog_middleware.dart Co-authored-by: Alejandro Santiago --- packages/dart_frog_lint/lib/src/dart_frog_middleware.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart b/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart index d6e84f72f..6eeb9a17f 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_middleware.dart @@ -5,7 +5,7 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:dart_frog_lint/src/types.dart'; /// {@template dart_frog_lint.request} -/// The definition of `dart_frog_request` lints. +/// The definition of `dart_frog_middleware` lints. /// {@endtemplate} class DartFrogMiddleware extends DartLintRule { /// {@macro dart_frog_lint.request} From 74a424f14f313208ce7a31dc08efc98fd2a2fb1f Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 4 Apr 2023 14:02:20 +0200 Subject: [PATCH 08/16] Update packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart Co-authored-by: Alejandro Santiago --- packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart index 5e1978037..c42dc2b97 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart @@ -81,7 +81,7 @@ class DartFrogEntrypoint extends DartLintRule { !httpServerTypeChecker.isExactlyType( (returnType as InterfaceType).typeArguments.single, )) { - // The parameter is not a Handler + // The parameter is not a HttpServer reporter.reportErrorForNode(code, run); return; } From 02e9f95051aa00319e19d42ceb3260aa33754d9b Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 4 Apr 2023 14:02:27 +0200 Subject: [PATCH 09/16] Update packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart Co-authored-by: Alejandro Santiago --- packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart index c42dc2b97..319fbccb7 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart @@ -70,7 +70,7 @@ class DartFrogEntrypoint extends DartLintRule { } if (portType?.isDartCoreInt != true) { - // The parameter is not a Handler + // The parameter is not a int reporter.reportErrorForNode(code, run); return; } From 1f8ee195d27c9e0c4dadaf48a632583433a7d6e5 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 4 Apr 2023 14:02:34 +0200 Subject: [PATCH 10/16] Update packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart Co-authored-by: Alejandro Santiago --- packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart index 319fbccb7..bc24bcb65 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart @@ -64,7 +64,7 @@ class DartFrogEntrypoint extends DartLintRule { } if (ipType == null || !internetAddressTypeChecker.isExactlyType(ipType)) { - // The parameter is not a Handler + // The parameter is not an InternetAddress reporter.reportErrorForNode(code, run); return; } From baa902ee88d55d5a0c73ed7e58227f004318f73c Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 4 Apr 2023 14:02:44 +0200 Subject: [PATCH 11/16] Update packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart Co-authored-by: Alejandro Santiago --- packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart index bc24bcb65..335f3d1e0 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart @@ -41,7 +41,7 @@ class DartFrogEntrypoint extends DartLintRule { } if (run.functionExpression.parameters?.parameters.length != 3) { - // Only one parameter is allowed + // Only three parameters are allowed reporter.reportErrorForNode(code, run); return; } From a31a0156de05a74a9a3e7ca70e378b84a5b43cf0 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 19 Apr 2023 14:26:22 +0200 Subject: [PATCH 12/16] Update packages/dart_frog_lint/test/src/parse_route_test.dart Co-authored-by: Alejandro Santiago --- packages/dart_frog_lint/test/src/parse_route_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart_frog_lint/test/src/parse_route_test.dart b/packages/dart_frog_lint/test/src/parse_route_test.dart index eae1ffc2e..bec00ec5e 100644 --- a/packages/dart_frog_lint/test/src/parse_route_test.dart +++ b/packages/dart_frog_lint/test/src/parse_route_test.dart @@ -3,7 +3,7 @@ import 'package:path/path.dart' as p; import 'package:test/test.dart'; void main() { - group(parseRoute, () { + group('parseRoute', () { test('throws if path is not within a "routes" directory', () { final path = p.join('path', 'to', 'file.dart'); expect(() => parseRoute(path), throwsArgumentError); From 2f339d9c997428c83d176b1d1da6c7f193ce830b Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 19 Apr 2023 14:34:41 +0200 Subject: [PATCH 13/16] Add issue link about missing exclude patterns --- packages/dart_frog_lint/lib/src/dart_frog_route.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dart_frog_lint/lib/src/dart_frog_route.dart b/packages/dart_frog_lint/lib/src/dart_frog_route.dart index 0d1327e42..8d06082dd 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_route.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_route.dart @@ -31,6 +31,7 @@ class DartFrogRequest extends DartLintRule { ) { // package:glob which filesToAnalyze uses does not seem to support exclude // patterns, so we have to manually filter out the _middleware.dart files + // See https://github.com/dart-lang/glob/issues/75 if (resolver.path.endsWith('_middleware.dart')) return; context.registry.addCompilationUnit((node) { From 3a3161ebb5ef006b13c2c31295282e76ab9540c1 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 19 Apr 2023 15:29:10 +0200 Subject: [PATCH 14/16] Add more entrypoint tests --- .../lib/src/dart_frog_entrypoint.dart | 4 +- packages/dart_frog_lint/lib/src/types.dart | 12 +- packages/dart_frog_lint/pubspec.yaml | 2 + .../incorrect_ip.dart | 9 ++ .../incorrect_sdk_ip.dart | 7 ++ .../non_future_result.dart | 7 ++ .../non_http_server_result.dart | 9 ++ .../non_server_io_result.dart | 7 ++ .../test/src/dart_frog_entrypoint_test.dart | 103 ++++++++++++++++++ 9 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_ip.dart create mode 100644 packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_sdk_ip.dart create mode 100644 packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_future_result.dart create mode 100644 packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_http_server_result.dart create mode 100644 packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_server_io_result.dart create mode 100644 packages/dart_frog_lint/test/src/dart_frog_entrypoint_test.dart diff --git a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart index 335f3d1e0..c2ab68d96 100644 --- a/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart +++ b/packages/dart_frog_lint/lib/src/dart_frog_entrypoint.dart @@ -63,7 +63,7 @@ class DartFrogEntrypoint extends DartLintRule { return; } - if (ipType == null || !internetAddressTypeChecker.isExactlyType(ipType)) { + if (ipType == null || !isInternetAddress(ipType)) { // The parameter is not an InternetAddress reporter.reportErrorForNode(code, run); return; @@ -78,7 +78,7 @@ class DartFrogEntrypoint extends DartLintRule { final returnType = run.returnType?.type; if (returnType == null || !returnType.isDartAsyncFuture || - !httpServerTypeChecker.isExactlyType( + !isHttpServer( (returnType as InterfaceType).typeArguments.single, )) { // The parameter is not a HttpServer diff --git a/packages/dart_frog_lint/lib/src/types.dart b/packages/dart_frog_lint/lib/src/types.dart index 498f1fed8..1ebf975b5 100644 --- a/packages/dart_frog_lint/lib/src/types.dart +++ b/packages/dart_frog_lint/lib/src/types.dart @@ -39,8 +39,16 @@ bool isHandler(DartType type) { return _handlerTypeChecker.isExactly(alias.element); } +bool _isFromDartSdk(DartType type) => type.element?.library?.isInSdk ?? false; + /// [TypeChecker] for `InternetAddress` -const internetAddressTypeChecker = TypeChecker.fromName('InternetAddress'); +bool isInternetAddress(DartType type) { + const nameChecker = TypeChecker.fromName('InternetAddress'); + return _isFromDartSdk(type) && nameChecker.isExactlyType(type); +} /// [TypeChecker] for `HttpServer` -const httpServerTypeChecker = TypeChecker.fromName('HttpServer'); +bool isHttpServer(DartType type) { + const nameChecker = TypeChecker.fromName('HttpServer'); + return _isFromDartSdk(type) && nameChecker.isExactlyType(type); +} diff --git a/packages/dart_frog_lint/pubspec.yaml b/packages/dart_frog_lint/pubspec.yaml index 878a9681a..cfcb7abe4 100644 --- a/packages/dart_frog_lint/pubspec.yaml +++ b/packages/dart_frog_lint/pubspec.yaml @@ -19,5 +19,7 @@ dependencies: path: ^1.8.3 dev_dependencies: + dart_frog: + path: ../dart_frog test: ^1.21.1 very_good_analysis: ^4.0.0 diff --git a/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_ip.dart b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_ip.dart new file mode 100644 index 000000000..772f08c78 --- /dev/null +++ b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_ip.dart @@ -0,0 +1,9 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +class InternetAddress {} + +Future run(Handler handler, InternetAddress ip, int port) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_sdk_ip.dart b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_sdk_ip.dart new file mode 100644 index 000000000..df89c6293 --- /dev/null +++ b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/incorrect_sdk_ip.dart @@ -0,0 +1,7 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +Future run(Handler handler, HttpServer ip, int port) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_future_result.dart b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_future_result.dart new file mode 100644 index 000000000..f4302a9f4 --- /dev/null +++ b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_future_result.dart @@ -0,0 +1,7 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +HttpServer run(Handler handler, InternetAddress ip, int port) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_http_server_result.dart b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_http_server_result.dart new file mode 100644 index 000000000..e5ac2132d --- /dev/null +++ b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_http_server_result.dart @@ -0,0 +1,9 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +class HttpServer {} + +Future run(Handler handler, InternetAddress ip, int port) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_server_io_result.dart b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_server_io_result.dart new file mode 100644 index 000000000..e1ad65c95 --- /dev/null +++ b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_sources/non_server_io_result.dart @@ -0,0 +1,7 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +Future run(Handler handler, InternetAddress ip, int port) { + throw UnimplementedError(); +} diff --git a/packages/dart_frog_lint/test/src/dart_frog_entrypoint_test.dart b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_test.dart new file mode 100644 index 000000000..84763ca70 --- /dev/null +++ b/packages/dart_frog_lint/test/src/dart_frog_entrypoint_test.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:dart_frog_lint/src/dart_frog_entrypoint.dart'; +import 'package:test/test.dart'; + +void main() { + group('dart_frog_entrypoint', () { + test( + 'Emits warning if the second parameter a class named InternetAddress ' + 'but not from dart:io', () async { + final file = File( + 'test/src/dart_frog_entrypoint_sources/incorrect_ip.dart', + ).absolute; + + final result = await resolveFile2(path: file.path); + result as ResolvedUnitResult; + + final error = await const DartFrogEntrypoint() + .testRun(result) + .then((e) => e.single); + + expect(error.message, 'Main files should define a valid "run" function.'); + expect(error.offset, 89); + expect(error.length, 103); + }); + + test( + 'Emits warning if the second parameter is class from dart:io but not ' + 'named InternetAddress', () async { + final file = File( + 'test/src/dart_frog_entrypoint_sources/incorrect_sdk_ip.dart', + ).absolute; + + final result = await resolveFile2(path: file.path); + result as ResolvedUnitResult; + + final error = await const DartFrogEntrypoint() + .testRun(result) + .then((e) => e.single); + + expect(error.message, 'Main files should define a valid "run" function.'); + expect(error.offset, 63); + expect(error.length, 98); + }); + + test('Emits warning if the result returns a non-future HttpServer', + () async { + final file = File( + 'test/src/dart_frog_entrypoint_sources/non_future_result.dart', + ).absolute; + + final result = await resolveFile2(path: file.path); + result as ResolvedUnitResult; + + final error = await const DartFrogEntrypoint() + .testRun(result) + .then((e) => e.single); + + expect(error.message, 'Main files should define a valid "run" function.'); + expect(error.offset, 63); + expect(error.length, 95); + }); + + test('Emits warning if the result returns a non Future result', + () async { + final file = File( + 'test/src/dart_frog_entrypoint_sources/non_http_server_result.dart', + ).absolute; + + final result = await resolveFile2(path: file.path); + result as ResolvedUnitResult; + + final error = await const DartFrogEntrypoint() + .testRun(result) + .then((e) => e.single); + + expect(error.message, 'Main files should define a valid "run" function.'); + expect(error.offset, 84); + expect(error.length, 103); + }); + + test( + 'Emits warning if the result returns a non Future,' + ' but the class is not a HttpServer', () async { + final file = File( + 'test/src/dart_frog_entrypoint_sources/non_server_io_result.dart', + ).absolute; + + final result = await resolveFile2(path: file.path); + result as ResolvedUnitResult; + + final error = await const DartFrogEntrypoint() + .testRun(result) + .then((e) => e.single); + + expect(error.message, 'Main files should define a valid "run" function.'); + expect(error.offset, 63); + expect(error.length, 108); + }); + }); +} From 0af05fea01014d83f7484ad2ab66112c492b75cc Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 19 Apr 2023 15:31:30 +0200 Subject: [PATCH 15/16] Add valid onRequest with parameter --- .../routes/dart_frog_route/parametrized_route/[userId3].dart | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId3].dart diff --git a/packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId3].dart b/packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId3].dart new file mode 100644 index 000000000..f80b3d3db --- /dev/null +++ b/packages/dart_frog_lint/example/routes/dart_frog_route/parametrized_route/[userId3].dart @@ -0,0 +1,5 @@ +import 'package:dart_frog/dart_frog.dart'; + +Response onRequest(RequestContext context, String userId3) { + return Response(); +} From b0114d9597ae1649caa3e73018788d8b4429c9b9 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 19 Apr 2023 15:32:11 +0200 Subject: [PATCH 16/16] Rename dart_frog_route -> dart_frog_request --- packages/dart_frog_lint/lib/dart_frog_lint.dart | 2 +- .../lib/src/{dart_frog_route.dart => dart_frog_request.dart} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/dart_frog_lint/lib/src/{dart_frog_route.dart => dart_frog_request.dart} (100%) diff --git a/packages/dart_frog_lint/lib/dart_frog_lint.dart b/packages/dart_frog_lint/lib/dart_frog_lint.dart index 63635c6d8..ea5200a70 100644 --- a/packages/dart_frog_lint/lib/dart_frog_lint.dart +++ b/packages/dart_frog_lint/lib/dart_frog_lint.dart @@ -1,7 +1,7 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:dart_frog_lint/src/dart_frog_entrypoint.dart'; import 'package:dart_frog_lint/src/dart_frog_middleware.dart'; -import 'package:dart_frog_lint/src/dart_frog_route.dart'; +import 'package:dart_frog_lint/src/dart_frog_request.dart'; /// The entrypoint of dart_frog_lint PluginBase createPlugin() => _DartFrogLintPlugin(); diff --git a/packages/dart_frog_lint/lib/src/dart_frog_route.dart b/packages/dart_frog_lint/lib/src/dart_frog_request.dart similarity index 100% rename from packages/dart_frog_lint/lib/src/dart_frog_route.dart rename to packages/dart_frog_lint/lib/src/dart_frog_request.dart