diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..20065d84
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,18 @@
+---
+name: Bug report
+about: There is a problem in how provider behaves
+title: ""
+labels: bug, needs triage
+assignees:
+ - rrousselGit
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+
+
+ custom_lint
+ Tools for building custom lint rules.
+
+ License +
+ +--- + + +
+
+
+
+
+ Built and maintained by Invertase. +
+ + +[analyzer_plugin]: https://pub.dev/packages/analyzer_plugin diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..fad807af --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,28 @@ +## Add built-in lints for highlighting invalid custom_lint & custom_lint_builder usage + +- no `bin/custom_lint.dart` found +- highlight in the IDE on the pubspec.yaml plugins that failed to start + +## Add custom_lint_test + +For simplifying testing plugins + +## Support disabling lint rules inside the analysis_options.yaml + +Such as: + +```yaml +linter: + rules: + require_trailing_commas: false + +custom_lint: + rules: + riverpod_final_provider: false +``` + +## Add support for refactors and fixes + +Instead of being limited to lints + +Bonus point for a `dart run custom_lint --fix` diff --git a/TASKS.md b/TASKS.md deleted file mode 100644 index d3c1bc22..00000000 --- a/TASKS.md +++ /dev/null @@ -1,49 +0,0 @@ -potential lint errors: - -- [ ] no bin found -- [ ] threw during start -- [ ] failed to connect -- [ ] unknown lint rule -- [ ] lint rule threw -- [ ] CLI executed vs local custom_lint version mismatch - - - - -features to have: - -- [x] IDE integration -- [x] support prints -- [x] built-in error reporting -- [ ] add built-in linter for providing warnings if a custom lint package is incorrectly setup -- [ ] CLI for running lints -- [ ] reactive syntax for source change -- [ ] hot reload or hot-restart -- [ ] handle ignores: - - [ ] `// ignore: lint` - - [ ] `// ignore_for_file: lint` - - [ ] `// ignore_for_file: type=lint` -- [ ] testing framework -- [ ] support analysis_options' configs: - - [ ] `import` - - [ ] `exclude` - - [ ] `include` -- [ ] add optional configuration file for changing the entrypoint location & passing options -- [ ] custom lint packages can specify default configs (rules enabled or disabled by default) -- [ ] unknown configs -- [ ] unknown rules - -How to deal with mono-repos? -Maybe: - -``` -packages/ - foo/ - pubspec.yaml -analysis_options.yaml -pubspec.yaml << depends on custom_lint -``` - -Things to consider: - -- a package may not depend on a specific rule diff --git a/all_lint_rules.yaml b/all_lint_rules.yaml index cdbe84e0..7733b5f1 100644 --- a/all_lint_rules.yaml +++ b/all_lint_rules.yaml @@ -3,10 +3,10 @@ linter: - always_declare_return_types - always_put_control_body_on_new_line - always_put_required_named_parameters_first - - always_require_non_null_named_parameters - always_specify_types - always_use_package_imports - annotate_overrides + - annotate_redeclares - avoid_annotating_with_dynamic - avoid_bool_literals_in_conditional_expressions - avoid_catches_without_on_clauses @@ -18,6 +18,7 @@ linter: - 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 @@ -31,8 +32,6 @@ linter: - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_future - avoid_returning_null_for_void - avoid_returning_this - avoid_setters_without_getters @@ -53,39 +52,56 @@ linter: - 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 + - document_ignores - empty_catches - empty_constructor_bodies - empty_statements + - eol_at_end_of_file - exhaustive_cases - file_names - flutter_style_todos - hash_and_equals - implementation_imports - - invariant_booleans - - iterable_contains_unrelated_type + - implicit_call_tearoffs + - implicit_reopen + - invalid_case_patterns + - invalid_runtime_check_with_js_interop_types - 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 - - list_remove_unrelated_type - literal_only_boolean_expressions + - matching_super_parameters + - missing_code_block_language_in_doc_comment - 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 @@ -110,7 +126,6 @@ linter: - prefer_constructors_over_static_methods - prefer_contains - prefer_double_quotes - - prefer_equal_for_default_values - prefer_expression_function_bodies - prefer_final_fields - prefer_final_in_for_each @@ -142,7 +157,9 @@ linter: - 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 @@ -153,15 +170,23 @@ linter: - tighten_type_of_initializing_formals - type_annotate_public_apis - type_init_formals + - type_literal_in_constant_pattern - unawaited_futures + - unintended_html_in_doc_comment - 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_library_name - 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 @@ -172,9 +197,14 @@ linter: - 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 @@ -186,6 +216,8 @@ linter: - use_rethrow_when_possible - use_setters_to_change_properties - use_string_buffers + - use_string_in_part_of_directives + - use_super_parameters - use_test_throws_matchers - use_to_and_as_if_applicable - valid_regexps diff --git a/analysis_options.yaml b/analysis_options.yaml index c2c1e38e..4e7216a2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,11 +1,9 @@ include: all_lint_rules.yaml analyzer: - exclude: - - "**/*.g.dart" - - "**/*.freezed.dart" - strong-mode: - implicit-casts: false - implicit-dynamic: false + language: + strict-casts: true + strict-inference: true + strict-raw-types: true errors: # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts. # We explicitly enabled even conflicting rules and are fixing the conflict @@ -16,9 +14,6 @@ analyzer: linter: rules: - # temporarily disabled - require_trailing_commas: false - # false positive one_member_abstracts: false @@ -57,10 +52,6 @@ linter: # and `@required Widget child` last. always_put_required_named_parameters_first: false - # `as` is not that bad (especially with the upcoming non-nullable types). - # Explicit exceptions is better than implicit exceptions. - avoid_as: false - # This project doesn't use Flutter-style todos flutter_style_todos: false diff --git a/docs/assists.md b/docs/assists.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/fixes.md b/docs/fixes.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/lints.md b/docs/lints.md new file mode 100644 index 00000000..e69de29b diff --git a/example_app/.dart_tool/package_config.json b/example_app/.dart_tool/package_config.json deleted file mode 100644 index aba5e5cc..00000000 --- a/example_app/.dart_tool/package_config.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "configVersion": 2, - "packages": [ - { - "name": "_fe_analyzer_shared", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-36.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "analyzer", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer-3.3.1", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "analyzer_plugin", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer_plugin-0.9.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "args", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/args-2.3.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "async", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/async-2.8.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "build", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/build-2.2.1", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "charcode", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "checked_yaml", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/checked_yaml-2.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "collection", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "convert", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/convert-3.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "crypto", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/crypto-3.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "custom_lint", - "rootUri": "../../packages/custom_lint", - "packageUri": "lib/", - "languageVersion": "2.16" - }, - { - "name": "custom_lint_builder", - "rootUri": "../../packages/custom_lint_builder", - "packageUri": "lib/", - "languageVersion": "2.16" - }, - { - "name": "dart_style", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/dart_style-2.2.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "file", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/file-6.1.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "glob", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/glob-2.0.2", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "json_annotation", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/json_annotation-4.4.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "logging", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/logging-1.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "meta", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "package_config", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/package_config-2.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "path", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/path-1.8.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "pub_semver", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pub_semver-2.1.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "pubspec_parse", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pubspec_parse-1.2.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "recase", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/recase-4.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "riverpod", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/riverpod-1.0.3", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "riverpod_lint", - "rootUri": "../../packages/riverpod_lint", - "packageUri": "lib/", - "languageVersion": "2.16" - }, - { - "name": "source_gen", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/source_gen-1.2.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "source_span", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.2", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "state_notifier", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/state_notifier-0.7.2+1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "string_scanner", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "target_lint", - "rootUri": "../../packages/target_lint", - "packageUri": "lib/", - "languageVersion": "2.16" - }, - { - "name": "term_glyph", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "typed_data", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "uuid", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/uuid-3.0.6", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "watcher", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/watcher-1.0.1", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "yaml", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/yaml-3.1.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "example_app", - "rootUri": "../", - "packageUri": "lib/", - "languageVersion": "2.16" - } - ], - "generated": "2022-03-16T12:41:01.245555Z", - "generator": "pub", - "generatorVersion": "2.16.1" -} diff --git a/example_app/.packages b/example_app/.packages deleted file mode 100644 index f1cdaaea..00000000 --- a/example_app/.packages +++ /dev/null @@ -1,43 +0,0 @@ -# This file is deprecated. Tools should instead consume -# `.dart_tool/package_config.json`. -# -# For more info see: https://dart.dev/go/dot-packages-deprecation -# -# Generated by pub on 2022-03-16 13:41:01.228084. -_fe_analyzer_shared:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-36.0.0/lib/ -analyzer:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer-3.3.1/lib/ -analyzer_plugin:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer_plugin-0.9.0/lib/ -args:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/args-2.3.0/lib/ -async:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/async-2.8.2/lib/ -build:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/build-2.2.1/lib/ -charcode:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/ -checked_yaml:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/checked_yaml-2.0.1/lib/ -collection:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/lib/ -convert:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/convert-3.0.1/lib/ -crypto:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/crypto-3.0.1/lib/ -custom_lint:../packages/custom_lint/lib/ -custom_lint_builder:../packages/custom_lint_builder/lib/ -dart_style:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/dart_style-2.2.2/lib/ -file:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/file-6.1.2/lib/ -glob:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/glob-2.0.2/lib/ -json_annotation:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/json_annotation-4.4.0/lib/ -logging:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/logging-1.0.2/lib/ -meta:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/ -package_config:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/package_config-2.0.2/lib/ -path:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/path-1.8.1/lib/ -pub_semver:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pub_semver-2.1.1/lib/ -pubspec_parse:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pubspec_parse-1.2.0/lib/ -recase:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/recase-4.0.0/lib/ -riverpod:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/riverpod-1.0.3/lib/ -riverpod_lint:../packages/riverpod_lint/lib/ -source_gen:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/source_gen-1.2.1/lib/ -source_span:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.2/lib/ -state_notifier:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/state_notifier-0.7.2+1/lib/ -string_scanner:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/ -target_lint:../packages/target_lint/lib/ -term_glyph:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/ -typed_data:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/ -uuid:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/uuid-3.0.6/lib/ -watcher:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/watcher-1.0.1/lib/ -yaml:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/yaml-3.1.0/lib/ -example_app:lib/ diff --git a/example_app/analysis_options.yaml b/example_app/analysis_options.yaml deleted file mode 100644 index 01a3c443..00000000 --- a/example_app/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: ../analysis_options.yaml -analyzer: - plugins: - - custom_lint \ No newline at end of file diff --git a/example_app/lib/main copy.dart b/example_app/lib/main copy.dart deleted file mode 100644 index e3726708..00000000 --- a/example_app/lib/main copy.dart +++ /dev/null @@ -1,3 +0,0 @@ -void main() { - print('hello wolrd'); -} diff --git a/example_app/pubspec.lock b/example_app/pubspec.lock deleted file mode 100644 index 522a3678..00000000 --- a/example_app/pubspec.lock +++ /dev/null @@ -1,257 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.dartlang.org" - source: hosted - version: "36.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "3.3.1" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.0" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.1" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.16.0" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - custom_lint: - dependency: "direct dev" - description: - path: "../packages/custom_lint" - relative: true - source: path - version: "0.0.1" - custom_lint_builder: - dependency: transitive - description: - path: "../packages/custom_lint_builder" - relative: true - source: path - version: "0.0.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.2" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.2" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.0" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - recase: - dependency: transitive - description: - name: recase - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - riverpod: - dependency: "direct main" - description: - name: riverpod - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - riverpod_lint: - dependency: "direct dev" - description: - path: "../packages/riverpod_lint" - relative: true - source: path - version: "0.0.1" - source_gen: - dependency: transitive - description: - name: source_gen - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.2" - state_notifier: - dependency: transitive - description: - name: state_notifier - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.2+1" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - target_lint: - dependency: "direct dev" - description: - path: "../packages/target_lint" - relative: true - source: path - version: "0.0.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.6" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" -sdks: - dart: ">=2.16.0 <3.0.0" diff --git a/example_app/pubspec.yaml b/example_app/pubspec.yaml deleted file mode 100644 index 64cae7db..00000000 --- a/example_app/pubspec.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: example_app -version: 0.0.1 -publish_to: none - -environment: - sdk: ">=2.16.0 <3.0.0" - -dependencies: - riverpod: ^1.0.3 - -dev_dependencies: - custom_lint: - path: ../packages/custom_lint - riverpod_lint: - path: ../packages/riverpod_lint - target_lint: - path: ../packages/target_lint diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 00000000..b087ccfa --- /dev/null +++ b/melos.yaml @@ -0,0 +1,6 @@ +name: custom_lint_workspace + +packages: + - packages/custom_lint* + - packages/custom_lint*/example** + - packages/lint_visitor_generator diff --git a/packages/custom_lint/.dart_tool/package_config.json b/packages/custom_lint/.dart_tool/package_config.json deleted file mode 100644 index 4c9f3308..00000000 --- a/packages/custom_lint/.dart_tool/package_config.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "configVersion": 2, - "packages": [ - { - "name": "_fe_analyzer_shared", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-36.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "analyzer", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer-3.3.1", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "analyzer_plugin", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer_plugin-0.9.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "args", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/args-2.3.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "async", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/async-2.8.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "charcode", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "checked_yaml", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/checked_yaml-2.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "collection", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "convert", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/convert-3.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "crypto", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/crypto-3.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "dart_style", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/dart_style-2.2.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "file", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/file-6.1.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "glob", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/glob-2.0.2", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "json_annotation", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/json_annotation-4.4.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "meta", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "package_config", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/package_config-2.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "path", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/path-1.8.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "pub_semver", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pub_semver-2.1.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "pubspec_parse", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pubspec_parse-1.2.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "recase", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/recase-4.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "source_span", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.2", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "string_scanner", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "term_glyph", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "typed_data", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "uuid", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/uuid-3.0.6", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "watcher", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/watcher-1.0.1", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "yaml", - "rootUri": "file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/yaml-3.1.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "custom_lint", - "rootUri": "../", - "packageUri": "lib/", - "languageVersion": "2.16" - } - ], - "generated": "2022-03-15T15:30:49.329921Z", - "generator": "pub", - "generatorVersion": "2.16.1" -} diff --git a/packages/custom_lint/.packages b/packages/custom_lint/.packages deleted file mode 100644 index d04cb270..00000000 --- a/packages/custom_lint/.packages +++ /dev/null @@ -1,34 +0,0 @@ -# This file is deprecated. Tools should instead consume -# `.dart_tool/package_config.json`. -# -# For more info see: https://dart.dev/go/dot-packages-deprecation -# -# Generated by pub on 2022-03-15 16:30:49.314008. -_fe_analyzer_shared:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-36.0.0/lib/ -analyzer:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer-3.3.1/lib/ -analyzer_plugin:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/analyzer_plugin-0.9.0/lib/ -args:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/args-2.3.0/lib/ -async:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/async-2.8.2/lib/ -charcode:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/ -checked_yaml:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/checked_yaml-2.0.1/lib/ -collection:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/lib/ -convert:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/convert-3.0.1/lib/ -crypto:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/crypto-3.0.1/lib/ -dart_style:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/dart_style-2.2.2/lib/ -file:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/file-6.1.2/lib/ -glob:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/glob-2.0.2/lib/ -json_annotation:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/json_annotation-4.4.0/lib/ -meta:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/ -package_config:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/package_config-2.0.2/lib/ -path:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/path-1.8.1/lib/ -pub_semver:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pub_semver-2.1.1/lib/ -pubspec_parse:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/pubspec_parse-1.2.0/lib/ -recase:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/recase-4.0.0/lib/ -source_span:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.2/lib/ -string_scanner:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/ -term_glyph:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/ -typed_data:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/ -uuid:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/uuid-3.0.6/lib/ -watcher:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/watcher-1.0.1/lib/ -yaml:file:///Users/remirousselet/.pub-cache/hosted/pub.dartlang.org/yaml-3.1.0/lib/ -custom_lint:lib/ diff --git a/packages/custom_lint/.pubignore b/packages/custom_lint/.pubignore new file mode 100644 index 00000000..7e9d7044 --- /dev/null +++ b/packages/custom_lint/.pubignore @@ -0,0 +1,3 @@ +# Ignoring the override file as it is useful for development only +# and require an absolute file path – which is unique to the developer. +tools/analyzer_plugin/pubspec_overrides.yaml diff --git a/packages/custom_lint/CHANGELOG.md b/packages/custom_lint/CHANGELOG.md new file mode 100644 index 00000000..60e613e2 --- /dev/null +++ b/packages/custom_lint/CHANGELOG.md @@ -0,0 +1,393 @@ +## 0.7.5 - 2025-02-27 + +Fix inconsistent version + +## 0.7.4 - 2025-02-27 + +- Upgrade Freezed to 3.0 +- Support Dart workspaces (thanks to @Rexios80) +- Suppport analyzer_plugin 0.13.0 (thanks to @Rexios80) +- Support custom hosted dependencies (thansk to @MobiliteDev) + +## 0.7.3 - 2025-02-08 + +- Bump analyzer_plugin + +## 0.7.2 - 2025-01-29 + +- Fix Android Studio/InteliJ (thanks to @EricSchlichting) + +## 0.7.1 - 2025-01-08 + +- Support analyzer 7.0.0 + +## 0.7.0 - 2024-10-27 + +- `custom_lint --fix` and the generated "Fix all" assists
+ now correctly handle imports.
+- Now supports a broad number of analyzer version.
+
+## 0.6.10 - 2024-10-10
+
+- Support installing custom_lint plugins in `dependencies:` instead of `dev_dependencies` (thanks to @dickermoshe).
+
+## 0.6.9 - 2024-10-09
+
+- `custom_lint_core` upgraded to `0.6.9`
+
+## 0.6.8 - 2024-10-08
+
+- Fix CI
+- Fix custom_lint not warning non-Dart files when necessary.
+- Custom_lint no-longer tries to analyze projects that lack a `.dart_tool/package_config.json`
+
+## 0.6.7 - 2024-09-08
+
+- Removed offline package resolution for the analyzer plugin.
+ The logic seemed broken at times, so removing it should make custom_lint more stable.
+
+## 0.6.6 - 2024-09-08
+
+- Fixed an error in the CLI when Flutter generates code under `.dart_tool/` or has dependencies on iOS libraries (thanks to @Kurogoma4D)
+
+## 0.6.5 - 2024-08-15
+
+- Upgraded to analyzer ^6.6.0.
+ This is a quick fix to unblock the stable Flutter channel.
+ A more robust fix will come later.
+- Fixed a bug where isSuperTypeOf throws if the element is null (thanks to @charlescyt)
+
+## 0.6.4 - 2024-03-16
+
+- Improve error message to attempt debugging a certain bug
+
+## 0.6.3 - 2024-03-16
+
+- Fixed Unimplemented error when running `pub get`.
+- Hot-reload and debug mode is now disabled by default.
+
+## 0.6.2 - 2024-02-19
+
+- `custom_lint --format json` no-longer outputs non-JSON logs (thanks to @kzrnm)
+- Upgrade analyzer to support 6.4.0
+- Fix null exception when using `TypeChecker.isSuperTypeOf` (thanks to @charlescyt)
+
+## 0.6.0 - 2024-02-04
+
+- Added support for `--fix`
+
+## 0.5.11 - 2024-01-27
+
+- Added support for `analysis_options.yaml` that are nt at the root of the project (thanks to @mrgnhnt96)
+
+## 0.5.8 - 2024-01-09
+
+- `// ignore` comments now correctly respect indentation when they are inserted (thanks to @PiotrRogulski)
+
+## 0.5.7 - 2023-11-20
+
+- Support JSON output format via CLI parameter `--format json|default` (thanks to @kuhnroyal)
+
+## 0.5.6 - 2023-10-30
+
+Optimized logic for finding an unused VM_service port.
+
+## 0.5.5 - 2023-10-26
+
+- Support `hotreloader` 4.0.0
+
+## 0.5.4 - 2023-10-20
+
+- Sort lints by severity in the command line (thanks to @kuhnroyal)
+- Fix watch mode not quitting with `q` (thanks to @kuhnroyal)
+- Improve the command line's output (thanks to @kuhnroyal)
+- Update uuid to 4.0.0
+- Fixed a port leak
+- Fix connection issues on Docker/windows (thanks to @hamsbrar)
+
+## 0.5.3 - 2023-08-29
+
+- The command line now supports ignoring warnings/infos with `--no-fatal-warnings`/`--no-fatal-infos` (thanks to @yamarkz)
+
+## 0.5.2 - 2023-08-16
+
+- Support both analyzer 5.12.0 and 6.0.0 at the same time.
+- Attempt at fixing the windows crash
+
+## 0.5.1 - 2023-08-03
+
+Support analyzer v6
+
+## 0.5.0 - 2023-06-21
+
+- Now resolves packages using `pub get` if custom_lint failed to resolve packages offline.
+ This should reduce the likelyness of a version conflict in mono-repositories.
+ The conflict may still happen if two projects in a mono-repo use incompatible
+ constraints. Like:
+ ```yaml
+ name: foo
+ dependencies:
+ package: ^1.0.0
+ ```
+ ```yaml
+ name: bar
+ dependencies:
+ package: ^2.0.0
+ ```
+- The command line now shows the lints' severity (thanks to @praxder)
+- Now requires Dart 3.0.0
+
+## 0.4.0 - 2023-05-12
+
+- Report uncaught exceptions inside `context.addPostRunCallback`
+- Added support for analyzer 5.12.0
+
+## 0.3.4 - 2023-04-19
+
+- custom_lint now automatically generate quick-fixes for "ignore for line/file".
+- Update the socket communication logic to avoid possible problem is the message
+ contains a \n.
+- fixes custom_lint on windows
+
+## 0.3.3 - 2023-04-06
+
+- Reduce the likelyness of a dependency version conflict.
+- Fix `dart analyze` crashing on large projects in the CI due to custom_lint
+ incorrectly trying to run plugins in debug mode.
+- Fix the `custom_lint` command line never terminating in some cases where plugins
+ fail to start (thanks to @kuhnroyal).
+- Upgraded `analyzer` to `>=5.7.0 <5.11.0`
+- `LintRuleNodeRegistry` and other AstVisitor-like now are based off `GeneralizingAstVisitor` instead of `GeneralizingAstVisitor`
+- Upgraded `cli_util` to `^0.4.0`
+- The command line no-longer throws if ran on an empty project or a project with
+ no plugins enabled
+- Exposes the Pubspec in CustomLintContext
+
+## 0.3.2 - 2023-03-09
+
+- Revert "Fixed an issue that caused a "Port already in use" error when
+ trying to start custom_lint".
+ This had the opposite effect of what's expected.
+
+## 0.3.0 - 2023-03-09
+
+- Update analyzer to >=5.7.0 <5.8.0
+- Fixed an issue that caused a "Port already in use" error when trying to
+ start custom_lint
+
+## 0.2.12
+
+Move json_serializable to dev dependencies
+
+## 0.2.11
+
+- Improved the error message when there is a version conflict in mono-repos (thanks to @@adsonpleal)
+- Bump minimum Dart SDK to `sdk: ">=2.19.0 <3.0.0"`
+
+## 0.2.5
+
+Fix custom_lint not correctly killing sub-processes when the IDE stops custom_lint.
+
+## 0.2.2
+
+Fixes an exception thrown when a project contains images.
+
+## 0.2.0
+
+**Large Breaking change**
+This new version introduces large changes to how lints/fixes/assists are defined.
+Long story short, besides the `createPlugin` method, the entire syntax changed.
+
+See the readme, examples, and docs around how to use the new syntax.
+
+The new syntax has multiple benefits:
+
+- It is now possible to enable/disable lints inside the `analysis_options.yaml`
+ as followed:
+
+ ```yaml
+ # optional
+ include: path/to/another/analysis_options.yaml
+
+ custom_lint:
+ rules:
+ # enable a lint rule
+ - my_lint_rule
+ # A lint rule that is explicitly disabled
+ - another_lint_rule: false
+ ```
+
+ Enabling/disabling lints is supported by default with the new syntax. Nothing to do~
+
+- Performance improvement when using a large number of lints.
+ The workload of analyzing files is now shared between lints.
+
+- The new syntax makes the code simpler to maintain.
+ Before, the `PluginBase.getLints` rapidly ended-up doing too much.
+ Now, it is simple to split the implementation in multiple bits
+
+## 0.1.2-dev
+
+Do some internal refactoring as an attempt to fix #60
+
+## 0.1.1
+
+- Fix an issue where plugins were hot-reloaded when the file analyzed changed.
+- Optimized analysis such that `PluginBase.getLints()` is theorically not reinvoked
+ unless the file analyzed changed.
+
+## 0.1.0
+
+- **Breaking**: The plugin entrypoint has moved.
+ Plugins no-longer should define a `/bin/custom_lint.dart` file.
+ Instead they should define a `/lib/.dart`
+
+- **Breaking**: The plugin entrypoint is modified. Plugins no-longer
+ define a "main", but instead define a `createPlugin` function:
+
+ Before:
+
+ ```dart
+ // /bin/custom_lint.dart
+ void main(List args, SendPort sendPort) {
+ startPlugin(sendPort, MyPlugin());
+ }
+ ```
+
+ After:
+
+ ```dart
+ // /lib/ MyPlugin();
+ ```
+
+- Add assist support.
+ Inside your plugins, you can now override `handleGetAssists`:
+
+ ```dart
+ import 'package:analyzer_plugin/protocol/protocol_generated.dart'
+ as analyzer_plugin;
+
+ class MyPlugin extends PluginBase {
+ // ...
+
+ Future handleGetAssists(
+ ResolvedUnitResult resolvedUnitResult, {
+ required int offset,
+ required int length,
+ }) async {
+ // TODO return some assists for the given offset
+ }
+ }
+ ```
+
+## 0.0.16
+
+Fix `expect_lint` not working if the file doesn't contain any lint.
+
+## 0.0.15
+
+- Custom_lint now has a built-in mechanism for testing lints.
+ Simply write a file that should contain lints for your plugin.
+ Then, using a syntax similar to `// ignore`, write a `// expect_lint: code`
+ in the line before your lint:
+
+ ```dart
+ // expect_lint: riverpod_final_provider
+ var provider = Provider(...);
+ ```
+
+ When doing this, there are two possible cases:
+
+ - The line after the `expect_lint` correctly contains the expected lint.
+ In that case, the lint is ignored (similarly to if we used `// ignore`)
+ - The next line does **not** contain the lint.
+ In that case, the `expect_lint` comment will have an error.
+
+ This allows testing your plugins by simply running `custom_lint` on your test/example folder.
+ Then, if any expected lint is missing, the command will fail. But if your plugin correctly
+ emits the lint, the command will succeed.
+
+- Upgrade analyzer/analzer_plugin
+
+## 0.0.14
+
+- Fix custom_lint not working in the IDE
+
+## 0.0.13
+
+- Add debugger and hot-reload support (Thanks to @TimWhiting)
+- Correctly respect `exclude` obtains from the analysis_options.yaml
+- Fix `dart analyze` incorrectly failing due to showing the "plugin is starting" lint.
+
+## 0.0.12
+
+- Fix custom_lint plugins not working in release mode and when using git dependencies (thanks to @TimWhiting)
+- Fix command line exit code not being set properly (thansk to @andrzejchm)
+
+## 0.0.11
+
+Fix custom_lint not showing in the IDE
+
+## 0.0.10+1
+
+Update docs
+
+## 0.0.10
+
+- Upgrade Riverpod to 2.0.0
+- Fix deprecation errors with analyzer
+
+## 0.0.9+1
+
+Update description and readme
+
+## 0.0.9
+
+- Lint fixes can now be used when placing the cursor on the last character of a lint
+- improve pub score
+
+## 0.0.8
+
+Allow lints to emit fixes
+
+## 0.0.7
+
+Fix a bug where the custom_lint command line may not list all lints
+
+## 0.0.6
+
+feat!: getLints now is expected to return a `Stream` instead of `Iterable`
+
+fix: a bug where the lints shown by the IDE could get out of sync with the actual content of the file
+
+## 0.0.5
+
+Fixed error reporting if a custom_lint plugin throws but the exception comes
+from a package instead of the plugin itself.
+
+## 0.0.4
+
+- Fixed a bug where the command line could show IDE-only meant for debugging
+
+## 0.0.3
+
+PluginBase.getLints now receive a `ResolvedUnitResult` instead of a `LibraryElement`.
+
+## 0.0.2
+
+- Compilation errors are now visible within the `pubspec.yaml` of applications
+ that are using the plugin.
+
+- Plugins that are currently loading are now highlighted inside the `pubspec.yaml`
+ of applications that are using the plugin.
+
+- If a plugin throws when trying to analyze a Dart file, the IDE will now
+ show the exception at the top of the analyzed file.
+
+- Compilation errors, exceptions and prints are now accessible within
+ a log file (`custom_lint.log`) inside applications using the plugin.
+
+## 0.0.1
+
+Initial release
diff --git a/packages/custom_lint/LICENSE b/packages/custom_lint/LICENSE
new file mode 100644
index 00000000..3f58cd65
--- /dev/null
+++ b/packages/custom_lint/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 Invertase Limited
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/custom_lint/README.md b/packages/custom_lint/README.md
new file mode 120000
index 00000000..fe840054
--- /dev/null
+++ b/packages/custom_lint/README.md
@@ -0,0 +1 @@
+../../README.md
\ No newline at end of file
diff --git a/packages/custom_lint/analysis_options.yaml b/packages/custom_lint/analysis_options.yaml
deleted file mode 100644
index 4ef3092e..00000000
--- a/packages/custom_lint/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: ../../analysis_options.yaml
-analyzer:
- plugins:
- - custom_lint
\ No newline at end of file
diff --git a/packages/custom_lint/bin/custom_lint.dart b/packages/custom_lint/bin/custom_lint.dart
new file mode 100644
index 00000000..87ac97b6
--- /dev/null
+++ b/packages/custom_lint/bin/custom_lint.dart
@@ -0,0 +1,85 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:custom_lint/custom_lint.dart';
+import 'package:custom_lint/src/output/output_format.dart';
+
+Future entrypoint([List args = const []]) async {
+ final parser = ArgParser()
+ ..addFlag(
+ 'fatal-infos',
+ help: 'Treat info level issues as fatal',
+ defaultsTo: true,
+ )
+ ..addFlag(
+ 'fatal-warnings',
+ help: 'Treat warning level issues as fatal',
+ defaultsTo: true,
+ )
+ ..addOption(
+ 'format',
+ valueHelp: 'value',
+ help: 'Specifies the format to display lints.',
+ defaultsTo: 'default',
+ allowed: [
+ OutputFormatEnum.plain.name,
+ OutputFormatEnum.json.name,
+ ],
+ allowedHelp: {
+ 'default':
+ 'The default output format. This format is intended to be user '
+ 'consumable.\nThe format is not specified and can change '
+ 'between releases.',
+ 'json': 'A machine readable output in a JSON format.',
+ },
+ )
+ ..addFlag(
+ 'watch',
+ help: "Watches plugins' sources and perform a hot-reload on change",
+ negatable: false,
+ )
+ ..addFlag(
+ 'fix',
+ help: 'Apply all possible fixes to the lint issues found.',
+ negatable: false,
+ )
+ ..addFlag(
+ 'help',
+ abbr: 'h',
+ negatable: false,
+ help: 'Prints command usage',
+ );
+ final result = parser.parse(args);
+
+ final help = result['help'] as bool;
+ if (help) {
+ stdout.writeln('Usage: custom_lint [--watch]');
+ stdout.writeln(parser.usage);
+ return;
+ }
+
+ final watchMode = result['watch'] as bool;
+ final fix = result['fix'] as bool;
+ final fatalInfos = result['fatal-infos'] as bool;
+ final fatalWarnings = result['fatal-warnings'] as bool;
+ final format = result['format'] as String;
+
+ await customLint(
+ workingDirectory: Directory.current,
+ watchMode: watchMode,
+ fatalInfos: fatalInfos,
+ fatalWarnings: fatalWarnings,
+ fix: fix,
+ format: OutputFormatEnum.fromName(format),
+ );
+}
+
+void main([List args = const []]) async {
+ try {
+ await entrypoint(args);
+ } finally {
+ // TODO figure out why this exit is necessary
+ exit(exitCode);
+ }
+}
diff --git a/packages/custom_lint/build.yaml b/packages/custom_lint/build.yaml
new file mode 100644
index 00000000..eeed2647
--- /dev/null
+++ b/packages/custom_lint/build.yaml
@@ -0,0 +1,7 @@
+targets:
+ $default:
+ builders:
+ source_gen|combining_builder:
+ options:
+ ignore_for_file:
+ - "type=lint"
diff --git a/packages/custom_lint/example/README.md b/packages/custom_lint/example/README.md
new file mode 100644
index 00000000..075a67c5
--- /dev/null
+++ b/packages/custom_lint/example/README.md
@@ -0,0 +1,3 @@
+# Custom Lint Example
+
+A simple example how powerful is custom_lint package.
diff --git a/packages/custom_lint/example/analysis_options.yaml b/packages/custom_lint/example/analysis_options.yaml
new file mode 100644
index 00000000..afa5cfd1
--- /dev/null
+++ b/packages/custom_lint/example/analysis_options.yaml
@@ -0,0 +1,11 @@
+include: ../../../analysis_options.yaml
+
+analyzer:
+ plugins:
+ - custom_lint
+
+linter:
+ rules:
+ public_member_api_docs: false
+ avoid_print: false
+ unreachable_from_main: false
diff --git a/packages/custom_lint/example/example.md b/packages/custom_lint/example/example.md
new file mode 100644
index 00000000..075a67c5
--- /dev/null
+++ b/packages/custom_lint/example/example.md
@@ -0,0 +1,3 @@
+# Custom Lint Example
+
+A simple example how powerful is custom_lint package.
diff --git a/packages/custom_lint/example/example_lint/analysis_options.yaml b/packages/custom_lint/example/example_lint/analysis_options.yaml
new file mode 100644
index 00000000..5a2532a3
--- /dev/null
+++ b/packages/custom_lint/example/example_lint/analysis_options.yaml
@@ -0,0 +1,6 @@
+# include: ../analysis_options.yaml
+
+# linter:
+# rules:
+# public_member_api_docs: false
+# avoid_print: false
diff --git a/packages/custom_lint/example/example_lint/lib/custom_lint_example_lint.dart b/packages/custom_lint/example/example_lint/lib/custom_lint_example_lint.dart
new file mode 100644
index 00000000..6647f974
--- /dev/null
+++ b/packages/custom_lint/example/example_lint/lib/custom_lint_example_lint.dart
@@ -0,0 +1,183 @@
+import 'package:analyzer/error/error.dart'
+ hide
+ // ignore: undefined_hidden_name, necessary to support lower analyzer versions
+ LintCode;
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:custom_lint_builder/custom_lint_builder.dart';
+
+/// This object is a utility for checking whether a Dart variable is assignable
+/// to a given class.
+///
+/// In this example, the class checked is `ProviderBase` from `package:riverpod`.
+const _providerBaseChecker = TypeChecker.fromName(
+ 'ProviderBase',
+ packageName: 'riverpod',
+);
+
+/// This is the entrypoint of our plugin.
+/// All plugins must specify a `createPlugin` function in their `lib/.dart` file
+PluginBase createPlugin() => _RiverpodLint();
+
+/// The class listing all the [LintRule]s and [Assist]s defined by our plugin.
+class _RiverpodLint extends PluginBase {
+ @override
+ List getLintRules(CustomLintConfigs configs) => [
+ PreferFinalProviders(),
+ ];
+
+ @override
+ List getAssists() => [_ConvertToStreamProvider()];
+}
+
+/// A custom lint rule.
+/// In our case, we want a lint rule which analyzes a Dart file. Therefore we
+/// subclass [DartLintRule].
+///
+/// For emitting lints on non-Dart files, subclass [LintRule].
+class PreferFinalProviders extends DartLintRule {
+ PreferFinalProviders() : super(code: _code);
+
+ /// Metadata about the lint define. This is the code which will show-up in the IDE,
+ /// and its description..
+ static const _code = LintCode(
+ name: 'riverpod_final_provider',
+ problemMessage: 'Providers should be declared using the `final` keyword.',
+ );
+
+ /// The core logic for our custom lint rule.
+ /// In our case, it will search over all variables defined in a Dart file and
+ /// search for the ones that implement a specific type (see [__providerBaseChecker]).
+ @override
+ void run(
+ // This object contains metadata about the analyzed file
+ CustomLintResolver resolver,
+ // ErrorReporter is for submitting lints. It contains utilities to specify
+ // where the lint should show-up.
+ ErrorReporter reporter,
+ // This contains various utilities, including tools for inspecting the content
+ // of Dart files in an efficient manner.
+ CustomLintContext context,
+ ) {
+ // Using this function, we search for [VariableDeclaration] reference the
+ // analyzed Dart file.
+ context.registry.addVariableDeclaration((node) {
+ final element = node.declaredElement;
+ if (element == null ||
+ element.isFinal ||
+ // We check that the variable is a Riverpod provider
+ !_providerBaseChecker.isAssignableFromType(element.type)) {
+ return;
+ }
+
+ // This emits our lint warning at the location of the variable.
+ reporter.atElement(element, code);
+ });
+ }
+
+ /// [LintRule]s can optionally specify a list of quick-fixes.
+ ///
+ /// Fixes will show-up in the IDE when the cursor is above the warning. And it
+ /// should contain a message explaining how the warning will be fixed.
+ @override
+ List getFixes() => [_MakeProviderFinalFix()];
+}
+
+/// We define a quick fix for an issue.
+///
+/// Our quick fix wants to analyze Dart files, so we subclass [DartFix].
+/// Fox quick-fixes on non-Dart files, see [Fix].
+class _MakeProviderFinalFix extends DartFix {
+ /// Similarly to [LintRule.run], [Fix.run] is the core logic of a fix.
+ /// It will take care or proposing edits within a file.
+ @override
+ void run(
+ CustomLintResolver resolver,
+ // Similar to ErrorReporter, ChangeReporter is an object used for submitting
+ // edits within a Dart file.
+ ChangeReporter reporter,
+ CustomLintContext context,
+ // This is the warning that was emitted by our [LintRule] and which we are
+ // trying to fix.
+ AnalysisError analysisError,
+ // This is the other warnings in the same file defined by our [LintRule].
+ // Useful in case we want to offer a "fix all" option.
+ List others,
+ ) {
+ // Using similar logic as in "PreferFinalProviders", we inspect the Dart file
+ // to search for variable declarations.
+ context.registry.addVariableDeclarationList((node) {
+ // We verify that the variable declaration is where our warning is located
+ if (!analysisError.sourceRange.intersects(node.sourceRange)) return;
+
+ // We define one edit, giving it a message which will show-up in the IDE.
+ final changeBuilder = reporter.createChangeBuilder(
+ message: 'Make provider final',
+ // This represents how high-low should this quick-fix show-up in the list
+ // of quick-fixes.
+ priority: 10,
+ );
+
+ // Our edit will consist of editing a Dart file, so we invoke "addDartFileEdit".
+ // The changeBuilder variable also has utilities for other types of files.
+ changeBuilder.addDartFileEdit((builder) {
+ final nodeKeyword = node.keyword;
+ final nodeType = node.type;
+ if (nodeKeyword != null) {
+ // Replace "var x = ..." into "final x = ...""
+
+ // Using "builder", we can emit changes to a file.
+ // In this case, addSimpleReplacement is used to override a selection
+ // with a new content.
+ builder.addSimpleReplacement(
+ SourceRange(nodeKeyword.offset, nodeKeyword.length),
+ 'final',
+ );
+ } else if (nodeType != null) {
+ // Replace "Type x = ..." into "final Type x = ..."
+
+ // Once again we emit an edit to our file.
+ // But this time, we add new content without replacing existing content.
+ builder.addSimpleInsertion(nodeType.offset, 'final ');
+ }
+ });
+ });
+ }
+}
+
+/// Using the same principle as we've seen before, we can define an "assist".
+///
+/// The main difference between an [Assist] and a [Fix] is that a [Fix] is associated
+/// with a problem. While an [Assist] is a change without an associated problem.
+///
+/// These are commonly known as "refactoring".
+class _ConvertToStreamProvider extends DartAssist {
+ @override
+ void run(
+ CustomLintResolver resolver,
+ ChangeReporter reporter,
+ CustomLintContext context,
+ SourceRange target,
+ ) {
+ context.registry.addVariableDeclaration((node) {
+ // Check that the visited node is under the cursor
+ if (!target.intersects(node.sourceRange)) return;
+
+ // verify that the visited node is a provider, to only show the assist on providers
+ final element = node.declaredElement;
+ if (element == null ||
+ element.isFinal ||
+ !_providerBaseChecker.isAssignableFromType(element.type)) {
+ return;
+ }
+
+ final changeBuilder = reporter.createChangeBuilder(
+ priority: 1,
+ message: 'Convert to StreamProvider',
+ );
+ changeBuilder.addDartFileEdit((builder) {
+ //
+ });
+ });
+ }
+}
diff --git a/packages/custom_lint/example/example_lint/pubspec.yaml b/packages/custom_lint/example/example_lint/pubspec.yaml
new file mode 100644
index 00000000..b51c0102
--- /dev/null
+++ b/packages/custom_lint/example/example_lint/pubspec.yaml
@@ -0,0 +1,14 @@
+name: custom_lint_example_lint
+publish_to: none
+
+environment:
+ sdk: '>=3.0.0 <4.0.0'
+
+dependencies:
+ analyzer: ^7.0.0
+ analyzer_plugin: ^0.13.0
+ custom_lint_builder:
+ path: ../../../custom_lint_builder
+
+dev_dependencies:
+ custom_lint:
diff --git a/packages/custom_lint/example/example_lint/pubspec_overrides.yaml b/packages/custom_lint/example/example_lint/pubspec_overrides.yaml
new file mode 100644
index 00000000..dc7d23ca
--- /dev/null
+++ b/packages/custom_lint/example/example_lint/pubspec_overrides.yaml
@@ -0,0 +1,8 @@
+# melos_managed_dependency_overrides: custom_lint,custom_lint_builder,custom_lint_core,custom_lint_visitor
+dependency_overrides:
+ custom_lint:
+ path: ../..
+ custom_lint_builder:
+ path: ../../../custom_lint_builder
+ custom_lint_core:
+ path: ../../../custom_lint_core
diff --git a/example_app/lib/main.dart b/packages/custom_lint/example/lib/main.dart
similarity index 64%
rename from example_app/lib/main.dart
rename to packages/custom_lint/example/lib/main.dart
index 1bd4ae7b..2f87b2af 100644
--- a/example_app/lib/main.dart
+++ b/packages/custom_lint/example/lib/main.dart
@@ -1,13 +1,15 @@
import 'package:riverpod/riverpod.dart';
void main() {
- print('hello wolrd');
+ print('hello world');
}
class Main {}
+// expect_lint: riverpod_final_provider
ProviderBase provider = Provider((ref) => 0);
+// expect_lint: riverpod_final_provider
Provider provider2 = Provider((ref) => 0);
Object? foo = 42;
diff --git a/packages/custom_lint/example/lib/test.jpeg b/packages/custom_lint/example/lib/test.jpeg
new file mode 100644
index 00000000..e853902d
Binary files /dev/null and b/packages/custom_lint/example/lib/test.jpeg differ
diff --git a/packages/custom_lint/example/pubspec.yaml b/packages/custom_lint/example/pubspec.yaml
new file mode 100644
index 00000000..47fac126
--- /dev/null
+++ b/packages/custom_lint/example/pubspec.yaml
@@ -0,0 +1,13 @@
+name: custom_lint_example_app
+publish_to: none
+
+environment:
+ sdk: '>=3.0.0 <4.0.0'
+
+dependencies:
+ riverpod: ^2.0.0
+
+dev_dependencies:
+ custom_lint:
+ custom_lint_example_lint:
+ path: ./example_lint
diff --git a/packages/custom_lint/example/pubspec_overrides.yaml b/packages/custom_lint/example/pubspec_overrides.yaml
new file mode 100644
index 00000000..53aa5353
--- /dev/null
+++ b/packages/custom_lint/example/pubspec_overrides.yaml
@@ -0,0 +1,10 @@
+# melos_managed_dependency_overrides: custom_lint,custom_lint_builder,custom_lint_core,custom_lint_example_lint,custom_lint_visitor
+dependency_overrides:
+ custom_lint:
+ path: ..
+ custom_lint_builder:
+ path: ../../custom_lint_builder
+ custom_lint_core:
+ path: ../../custom_lint_core
+ custom_lint_example_lint:
+ path: example_lint
diff --git a/packages/custom_lint/lib/basic_runner.dart b/packages/custom_lint/lib/basic_runner.dart
new file mode 100644
index 00000000..59b16ca7
--- /dev/null
+++ b/packages/custom_lint/lib/basic_runner.dart
@@ -0,0 +1,4 @@
+@Deprecated('Import `package:custom_lint/custom_lint.dart` instead')
+library;
+
+export 'custom_lint.dart';
diff --git a/packages/custom_lint/lib/custom_lint.dart b/packages/custom_lint/lib/custom_lint.dart
new file mode 100644
index 00000000..000efefa
--- /dev/null
+++ b/packages/custom_lint/lib/custom_lint.dart
@@ -0,0 +1,199 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:cli_util/cli_logging.dart';
+
+import 'src/cli_logger.dart';
+import 'src/output/output_format.dart';
+import 'src/output/render_lints.dart';
+import 'src/plugin_delegate.dart';
+import 'src/runner.dart';
+import 'src/server_isolate_channel.dart';
+import 'src/v2/custom_lint_analyzer_plugin.dart';
+import 'src/workspace.dart';
+
+const _help = '''
+
+Custom lint runner commands:
+r: Force re-lint
+q: Quit
+
+''';
+
+/// Runs plugins with custom_lint.dart on the given directory
+///
+/// In watch mode:
+/// * This will run until the user types q to quit
+/// * The plugin will hot-reload when the user changes it's code, and will cause a re-lint
+/// * The exit code is the one from the last lint before quitting
+/// * The user can force a reload by typing r
+///
+/// Otherwise:
+/// * There is no hot-reload or watching so linting only happens once
+/// * The process exits with the most recent result of the linter
+///
+/// Watch mode cannot be enabled if in release mode.
+Future customLint({
+ bool watchMode = true,
+ required Directory workingDirectory,
+ bool fatalInfos = true,
+ bool fatalWarnings = true,
+ OutputFormatEnum format = OutputFormatEnum.plain,
+ bool fix = false,
+}) async {
+ // Reset the code
+ exitCode = 0;
+
+ final channel = ServerIsolateChannel();
+ try {
+ await _runServer(
+ channel,
+ watchMode: watchMode,
+ workingDirectory: workingDirectory,
+ fatalInfos: fatalInfos,
+ fatalWarnings: fatalWarnings,
+ format: format,
+ fix: fix,
+ );
+ } catch (_) {
+ exitCode = 1;
+ } finally {
+ await channel.close();
+ }
+}
+
+Future _runServer(
+ ServerIsolateChannel channel, {
+ required bool watchMode,
+ required Directory workingDirectory,
+ required bool fatalInfos,
+ required bool fatalWarnings,
+ required OutputFormatEnum format,
+ required bool fix,
+}) async {
+ final customLintServer = await CustomLintServer.start(
+ sendPort: channel.receivePort.sendPort,
+ watchMode: watchMode,
+ fix: fix,
+ workingDirectory: workingDirectory,
+ // In the CLI, only show user defined lints. Errors & logs will be
+ // rendered separately
+ includeBuiltInLints: false,
+ delegate: CommandCustomLintDelegate(),
+ );
+
+ await CustomLintServer.runZoned(() => customLintServer, () async {
+ CustomLintRunner? runner;
+
+ try {
+ final workspace = await CustomLintWorkspace.fromPaths(
+ [workingDirectory.path],
+ workingDirectory: workingDirectory,
+ );
+ runner = CustomLintRunner(customLintServer, workspace, channel);
+
+ await runner.initialize;
+
+ final log = CliLogger();
+ final progress =
+ format == OutputFormatEnum.json ? null : log.progress('Analyzing');
+
+ await _runPlugins(
+ runner,
+ log: log,
+ progress: progress,
+ reload: false,
+ workingDirectory: workingDirectory,
+ fatalInfos: fatalInfos,
+ fatalWarnings: fatalWarnings,
+ format: format,
+ );
+
+ if (watchMode) {
+ await _startWatchMode(
+ runner,
+ log: log,
+ workingDirectory: workingDirectory,
+ fatalInfos: fatalInfos,
+ fatalWarnings: fatalWarnings,
+ format: format,
+ );
+ }
+ } finally {
+ await runner?.close();
+ }
+ }).whenComplete(() async {
+ // Closing the server output of "runZoned" to ensure that "runZoned" completes
+ // before the server is closed.
+ // Failing to do so could cause exceptions within "runZoned" to be handled
+ // after the server is closed, preventing the exception from being printed.
+ await customLintServer.close();
+ });
+}
+
+Future _runPlugins(
+ CustomLintRunner runner, {
+ required Logger log,
+ required bool reload,
+ required Directory workingDirectory,
+ required bool fatalInfos,
+ required bool fatalWarnings,
+ required OutputFormatEnum format,
+ Progress? progress,
+}) async {
+ final lints = await runner.getLints(reload: reload);
+
+ renderLints(
+ lints,
+ log: log,
+ progress: progress,
+ workingDirectory: workingDirectory,
+ fatalInfos: fatalInfos,
+ fatalWarnings: fatalWarnings,
+ format: format,
+ );
+}
+
+Future _startWatchMode(
+ CustomLintRunner runner, {
+ required Logger log,
+ required Directory workingDirectory,
+ required bool fatalInfos,
+ required bool fatalWarnings,
+ required OutputFormatEnum format,
+}) async {
+ if (stdin.hasTerminal) {
+ stdin
+ // Let's not pollute the output with whatever the user types
+ ..echoMode = false
+ // Let's not force user to have to press "enter" to input a command
+ ..lineMode = false;
+ }
+
+ log.stdout(_help);
+
+ // Handle user inputs, forcing the command to continue until the user asks to "quit"
+ await for (final input in stdin.transform(utf8.decoder)) {
+ switch (input) {
+ case 'r':
+ // Rerunning lints
+ final progress = log.progress('Manual re-lint');
+ await _runPlugins(
+ runner,
+ log: log,
+ progress: progress,
+ reload: true,
+ workingDirectory: workingDirectory,
+ fatalInfos: fatalInfos,
+ fatalWarnings: fatalWarnings,
+ format: format,
+ );
+ case 'q':
+ // Let's quit the command line
+ return;
+ default:
+ // Unknown command. Nothing to do
+ }
+ }
+}
diff --git a/packages/custom_lint/lib/protocol.dart b/packages/custom_lint/lib/protocol.dart
deleted file mode 100644
index 8698faf8..00000000
--- a/packages/custom_lint/lib/protocol.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-import 'package:analyzer_plugin/protocol/protocol.dart';
-
-class PrintParams {
- PrintParams(this.message);
-
- factory PrintParams.fromNotification(Notification notification) {
- assert(
- notification.event == key,
- 'Notification is not a print notification',
- );
-
- return PrintParams(notification.params!['message']! as String);
- }
-
- static const key = 'custom_lint.print';
-
- final String message;
-
- Notification toNotification() {
- return Notification(key, {'message': message});
- }
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin/analyzer_plugin.dart b/packages/custom_lint/lib/src/analyzer_plugin/analyzer_plugin.dart
deleted file mode 100644
index f417d40f..00000000
--- a/packages/custom_lint/lib/src/analyzer_plugin/analyzer_plugin.dart
+++ /dev/null
@@ -1,514 +0,0 @@
-// ignore_for_file: public_member_api_docs
-
-import 'dart:async';
-import 'dart:io';
-
-import 'package:analyzer/file_system/file_system.dart' as analyzer;
-import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
-import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
-import 'package:analyzer_plugin/protocol/protocol_common.dart';
-import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
-import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin
- show RequestParams;
-import 'package:package_config/package_config_types.dart';
-import 'package:path/path.dart' as p;
-import 'package:pub_semver/pub_semver.dart';
-import 'package:pubspec_parse/pubspec_parse.dart';
-import 'package:uuid/uuid.dart';
-
-import '../log.dart';
-import 'my_server_plugin.dart';
-import 'plugin_link.dart';
-
-const _uui = Uuid();
-
-class CustomLintPlugin extends MyServerPlugin {
- CustomLintPlugin(analyzer.ResourceProvider provider) : super(provider);
-
- @override
- String get contactInfo => 'https://github.com/invertase/custom_lint/issues';
-
- @override
- List get fileGlobsToAnalyze => const ['*.dart'];
-
- @override
- String get name => 'custom_lint';
-
- @override
- String get version => '1.0.0-alpha.0';
-
- // Storing the future directly to avoid race conditions
- final _pluginLinks = {};
-
- /// A table mapping the current context roots to the analysis driver created
- /// for that root.
- final _activeContextRoots = {};
-
- late plugin.PluginVersionCheckParams _versionCheckRequest;
- plugin.AnalysisSetPriorityFilesParams? _lastSetPriorityFilesRequest;
-
- @override
- Future handleAnalysisSetContextRoots(
- plugin.AnalysisSetContextRootsParams parameters,
- ) async {
- // Context roots have changed, so we spawn/kill plugins associated with
- // the new/removed roots.
-
- final contextRoots = parameters.roots;
- final oldRoots = _activeContextRoots.toList();
-
- // Let's spawn new plugins for new context roots
- final newPluginKeys = contextRoots.expand((contextRoot) {
- // contextRoot was already initialized, so we don't re-create it
- if (oldRoots.remove(contextRoot)) return const [];
-
- _activeContextRoots.add(contextRoot);
- return _spawnNewPluginsForContext(contextRoot).keys;
- }).toList();
-
- // Initializing new plugins, calling version check + set context roots + initial priority files
- // TODO use Future.wait
- // TODO guard errors
- for (final linkKey in newPluginKeys) {
- // TODO close subscribption
- _pluginLinks[linkKey]!
- ..messages.listen((event) {
- final file = File('${linkKey.toFilePath()}/log.txt');
- file.writeAsStringSync(
- event.message + '\n',
- mode: FileMode.append,
- );
- })
- ..error.listen((event) {
- final file = File('${linkKey.toFilePath()}/log.txt');
- file.writeAsStringSync(
- '${event.message}\n${event.stackTrace}\n',
- mode: FileMode.append,
- );
- })
- ..notifications.listen((event) {
- _handleNotification(event, linkKey);
- });
-
- // TODO what if setContextRoot or priotity files changes while these
- // requests are pending?
- await _requestPlugin(linkKey, _versionCheckRequest);
-
- // TODO filter events if the previous/new values are the same
- // Call setContextRoots on the plugin with only the roots that have
- // the plugin enabled
- await _requestPlugin(
- linkKey,
- plugin.AnalysisSetContextRootsParams(
- parameters.roots
- .where((contextRoot) =>
- _pluginLinks[linkKey]!.contextRoots.contains(contextRoot))
- .toList(),
- ),
- );
-
- final priorityFilesParam = _priorityFilesForPlugin(linkKey);
- if (priorityFilesParam != null) {
- await _requestPlugin(linkKey, priorityFilesParam);
- }
- }
-
- // TODO handle context root change on already existing plugins
- // Killing unused plugins from removed roots
- await Future.wait(
- oldRoots.map((contextRoot) {
- // The context has been removed, so we kill the plugin.
- if (_activeContextRoots.remove(contextRoot)) {
- return _disposePluginsForContext(contextRoot);
- }
- return Future.value();
- }),
- );
-
- return plugin.AnalysisSetContextRootsResult();
- }
-
- Future _disposePluginsForContext(
- plugin.ContextRoot contextRoot,
- ) {
- // Remove the context root and stop all plugins that are no-longer
- // associated with any context root.
-
- return Future.wait([
- for (final pluginLink in _pluginLinks.values.toList())
- if (pluginLink.contextRoots.remove(contextRoot) &&
- pluginLink.contextRoots.isEmpty)
- _pluginLinks.remove(pluginLink.key)!.close(),
- ]);
- }
-
- Map _spawnNewPluginsForContext(
- plugin.ContextRoot pluginContextRoot,
- ) {
- final result = {};
-
- try {
- for (final plugin in _getPluginsForContext(pluginContextRoot)) {
- if (!_pluginLinks.containsKey(plugin.root)) {
- runZonedGuarded(() {
- result[plugin.root] =
- _pluginLinks[plugin.root] = PluginLink.spawn(plugin.root);
- }, (err, stack) {
- log('Error while spawning isolate $err \n $stack');
- });
-
- final pubspecPath = p.join(
- plugin.root.toFilePath(),
- 'pubspec.yaml',
- );
-
- log('warning at $pubspecPath');
-
- // try {
- // void fn() {
- // channel.sendNotification(
- // plugin.AnalysisErrorsParams(
- // pubspecPath,
- // [
- // // plugin.AnalysisError(
- // // // TODO use plugin.class
- // // AnalysisErrorSeverity.WARNING,
- // // AnalysisErrorType.LINT,
- // // plugin.Location(pubspecPath, 0, 100, 0, 0),
- // // 'Invalid pubspec format',
- // // 'custom_lint_setup',
- // // ),
- // ],
- // ).toNotification(),
- // );
- // }
-
- // // Timer(Duration(seconds: 10), fn);
- // } catch (err, stack) {
- // log('failed to do something $err\n$stack');
- // }
- }
-
- result[plugin.root]!.contextRoots.add(pluginContextRoot);
- }
-
- return result;
- } catch (err, stack) {
- log('Failed to start plugins2:\n$err\n$stack\n\n');
- channel.sendNotification(
- plugin.PluginErrorParams(
- true,
- err.toString(),
- stack.toString(),
- ).toNotification(),
- );
- rethrow;
- }
- }
-
- Iterable _getPluginsForContext(
- plugin.ContextRoot contextRoot,
- ) sync* {
- final packagePath = contextRoot.root;
- // TODO if it is a plugin definition, assert that it contains the necessary configs
-
- // TODO is it safe to assume that there will always be a pubspec at the root?
- // TODO will there be packages nested in this directory, or will analyzer_plugin spawn a new plugin?
- // TODO should we listen to source changes for pubspec change/creation?
- final pubspec = _loadPubspecAt(packagePath);
-
- log('Got package ${pubspec.name}');
-
- final packageConfigFile = File(
- p.join(packagePath, '.dart_tool', 'package_config.json'),
- );
-
- if (!packageConfigFile.existsSync()) {
- // TODO should we listen to source changes for a late pub get and reload?
- throw StateError(
- 'No ${packageConfigFile.path} found. Make sure to run `pub get` first.',
- );
- }
-
- final packageConfig = PackageConfig.parseString(
- packageConfigFile.readAsStringSync(),
- packageConfigFile.uri,
- );
-
- for (final dependency in {
- ...pubspec.dependencies,
- ...pubspec.devDependencies,
- ...pubspec.dependencyOverrides
- }.entries) {
- final dependencyMeta = packageConfig.packages.firstWhere(
- (package) => package.name == dependency.key,
- orElse: () => throw StateError(
- 'Failed to find the source for ${dependency.key}. '
- 'Make sure to run `pub get`.',
- ),
- );
-
- final dependencyPubspec =
- _loadPubspecAt(dependencyMeta.root.toFilePath());
-
-// TODO extract magic value
- if (dependencyPubspec.hasDependency('custom_lint_builder')) {
- yield dependencyMeta;
- log('found plugin for ${dependency.key}: ${dependencyPubspec.name}');
- // TODO assert that they have the necessary configs
-
- log('spawning plugin: ${dependencyPubspec.name}');
- }
- }
- }
-
- void _handleNotification(plugin.Notification notification, Uri pluginKey) {
- // TODO try/catch
- switch (notification.event) {
- case 'analysis.errors':
- final link = _pluginLinks[pluginKey]!;
- final params =
- plugin.AnalysisErrorsParams.fromNotification(notification);
-
- if (!p.isAbsolute(params.file)) {
- throw StateError('${params.file} is not an absolute path');
- }
-
- // TODO why are all files re-analyzed when a single file changes?
- // TODO handle removed files or there is otherwise a memory leak
- link.lintsForLibrary[params.file] = params;
-
- final lintsForFile = _pluginLinks.values
- .expand(
- (link) => link.lintsForLibrary[params.file]?.errors ?? const [],
- )
- .toList();
-
- log('got lints for ${params.file}: ${lintsForFile.map((e) => e.code)}');
-
- channel.sendNotification(
- plugin.AnalysisErrorsParams(
- params.file,
- lintsForFile,
- ).toNotification(),
- );
- break;
- default:
- channel.sendNotification(notification);
- break;
- }
- }
-
- Future> _requestAllPlugins(
- plugin.RequestParams request,
- ) {
- return Future.wait(
- _pluginLinks.keys.map((key) => _requestPlugin(key, request)),
- );
- }
-
- Future _requestPlugin(
- Uri pluginKey,
- plugin.RequestParams request,
- ) async {
- assert(
- _pluginLinks.containsKey(pluginKey),
- 'Bad state, plugin $pluginKey not found',
- );
- final link = _pluginLinks[pluginKey]!;
- final id = _uui.v4();
-
- final response = link.responses.firstWhere((message) => message.id == id);
- link.send(request.toRequest(id).toJson());
- return response;
- }
-
- @override
- Future handleEditGetFixes(
- plugin.EditGetFixesParams parameters,
- ) async {
- final responses = await _requestAllPlugins(parameters);
-
- return plugin.EditGetFixesResult(
- responses
- .map(plugin.EditGetFixesResult.fromResponse)
- .expand((e) => e.fixes)
- .toList(),
- );
- }
-
- @override
- FutureOr handlePluginVersionCheck(
- plugin.PluginVersionCheckParams parameters,
- ) {
- _versionCheckRequest = parameters;
-
- final versionString = parameters.version;
- final serverVersion = Version.parse(versionString);
- // TODO does this needs to be deferred to plugins?
- return plugin.PluginVersionCheckResult(
- isCompatibleWith(serverVersion),
- name,
- version,
- fileGlobsToAnalyze,
- contactInfo: contactInfo,
- );
- }
-
- @override
- FutureOr
- handleAnalysisHandleWatchEvents(
- plugin.AnalysisHandleWatchEventsParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.AnalysisHandleWatchEventsResult();
- }
-
- plugin.AnalysisSetPriorityFilesParams? _priorityFilesForPlugin(
- Uri pluginKey,
- ) {
- final allPriorityFiles = _lastSetPriorityFilesRequest?.files;
- if (allPriorityFiles == null) return null;
-
- final link = _pluginLinks[pluginKey];
- if (link == null) {
- throw StateError('Plugin $pluginKey not found');
- }
-
- final priorityFilesForPlugin = allPriorityFiles.where(
- (priorityFile) {
- return link.contextRoots.any(
- (contextRoot) => p.isWithin(contextRoot.root, priorityFile),
- );
- },
- ).toList();
-
- return plugin.AnalysisSetPriorityFilesParams(priorityFilesForPlugin);
- }
-
- @override
- FutureOr
- handleAnalysisSetPriorityFiles(
- plugin.AnalysisSetPriorityFilesParams parameters,
- ) async {
- // TODO verify priority files are part of the context roots associated with the plugin
- _lastSetPriorityFilesRequest = parameters;
-
- await Future.wait(
- _pluginLinks.entries.map(
- (entry) =>
- // TODO filter request if previous/new values are the same
- _requestPlugin(entry.key, _priorityFilesForPlugin(entry.key)!),
- ),
- );
-
- return plugin.AnalysisSetPriorityFilesResult();
- }
-
- @override
- FutureOr
- handleAnalysisSetSubscriptions(
- plugin.AnalysisSetSubscriptionsParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.AnalysisSetSubscriptionsResult();
- }
-
- @override
- FutureOr handleAnalysisUpdateContent(
- plugin.AnalysisUpdateContentParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.AnalysisUpdateContentResult();
- }
-
- @override
- FutureOr
- handleCompletionGetSuggestions(
- plugin.CompletionGetSuggestionsParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.CompletionGetSuggestionsResult(
- -1,
- -1,
- const [],
- );
- }
-
- @override
- FutureOr handleEditGetAssists(
- plugin.EditGetAssistsParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.EditGetAssistsResult(
- const [],
- );
- }
-
- @override
- FutureOr
- handleEditGetAvailableRefactorings(
- plugin.EditGetAvailableRefactoringsParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.EditGetAvailableRefactoringsResult(
- const [],
- );
- }
-
- @override
- FutureOr handleEditGetRefactoring(
- plugin.EditGetRefactoringParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return null;
- }
-
- @override
- Future handleAnalysisGetNavigation(
- plugin.AnalysisGetNavigationParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.AnalysisGetNavigationResult(
- [],
- [],
- [],
- );
- }
-
- @override
- FutureOr handleKytheGetKytheEntries(
- plugin.KytheGetKytheEntriesParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return null;
- }
-
- @override
- FutureOr handlePluginShutdown(
- plugin.PluginShutdownParams parameters,
- ) async {
- await _requestAllPlugins(parameters);
- return plugin.PluginShutdownResult();
- }
-}
-
-Pubspec _loadPubspecAt(String packagePath) {
- final pubspecFile = File(p.join(packagePath, 'pubspec.yaml'));
- if (!pubspecFile.existsSync()) {
- throw StateError('No pubspec.yaml found at $packagePath.');
- }
-
- return Pubspec.parse(
- pubspecFile.readAsStringSync(),
- sourceUrl: pubspecFile.uri,
- );
-}
-
-extension on Pubspec {
- bool hasDependency(String name) {
- return dependencies.containsKey(name) ||
- devDependencies.containsKey(name) ||
- dependencyOverrides.containsKey(name);
- }
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin/analyzer_plugin_starter.dart b/packages/custom_lint/lib/src/analyzer_plugin/analyzer_plugin_starter.dart
deleted file mode 100644
index 7252dfc7..00000000
--- a/packages/custom_lint/lib/src/analyzer_plugin/analyzer_plugin_starter.dart
+++ /dev/null
@@ -1,18 +0,0 @@
-import 'dart:io';
-import 'dart:isolate';
-
-import 'package:analyzer/file_system/physical_file_system.dart';
-import 'package:analyzer_plugin/starter.dart';
-import 'package:custom_lint/src/analyzer_plugin/custom_server_plugin_starter.dart';
-
-import '../log.dart';
-
-import 'analyzer_plugin.dart';
-
-void start(Iterable _, SendPort sendPort) {
- log('Start custom_plugin');
-
- // Server(sendPort, CustomLintPlugin(PhysicalResourceProvider.INSTANCE)).start();
- MyServerPluginStarter(CustomLintPlugin(PhysicalResourceProvider.INSTANCE))
- .start(sendPort);
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin/custom_server_plugin_starter.dart b/packages/custom_lint/lib/src/analyzer_plugin/custom_server_plugin_starter.dart
deleted file mode 100644
index a8d16f11..00000000
--- a/packages/custom_lint/lib/src/analyzer_plugin/custom_server_plugin_starter.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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 'dart:isolate';
-
-import 'package:analyzer_plugin/plugin/plugin.dart';
-import 'package:analyzer_plugin/starter.dart';
-
-import 'my_isolate_channel.dart';
-import 'my_server_plugin.dart';
-
-/// The [Driver] class represents a single running instance of an analysis
-/// server plugin. It is responsible for handling the communications with the
-/// server and forwarding requests on to the plugin.
-class MyServerPluginStarter implements ServerPluginStarter {
- /// The plugin that will be started.
- final MyServerPlugin plugin;
-
- /// Initialize a newly created driver that can be used to start the given
- /// plugin.
- MyServerPluginStarter(this.plugin);
-
- @override
- void start(SendPort sendPort) {
- var channel = PluginIsolateChannel(sendPort);
- plugin.start(channel);
- }
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin/my_isolate_channel.dart b/packages/custom_lint/lib/src/analyzer_plugin/my_isolate_channel.dart
deleted file mode 100644
index 3cc053ec..00000000
--- a/packages/custom_lint/lib/src/analyzer_plugin/my_isolate_channel.dart
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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 'dart:async';
-import 'dart:isolate';
-
-import 'package:analyzer_plugin/channel/channel.dart';
-import 'package:analyzer_plugin/protocol/protocol.dart';
-
-class PluginIsolateChannel implements PluginCommunicationChannel {
- /// Initialize a newly created channel to communicate with the server.
- PluginIsolateChannel(this._sendPort) {
- _receivePort = ReceivePort();
- _sendPort.send(_receivePort.sendPort);
- }
-
- /// The port used to send notifications and responses to the server.
- final SendPort _sendPort;
-
- /// The port used to receive requests from the server.
- late final ReceivePort _receivePort;
-
- /// The subscription that needs to be cancelled when the channel is closed.
- StreamSubscription? _subscription;
-
- @override
- void close() {
- _subscription?.cancel();
- _subscription = null;
- }
-
- @override
- void listen(
- void Function(Request request) onRequest, {
- Function? onError,
- void Function()? onDone,
- }) {
- void onData(Object? data) {
- final requestMap = data! as Map;
- final request = Request.fromJson(requestMap);
- onRequest(request);
- }
-
- if (_subscription != null) {
- throw StateError('Only one listener is allowed per channel');
- }
- _subscription = _receivePort.listen(
- onData,
- onError: onError,
- onDone: onDone,
- cancelOnError: false,
- );
- }
-
- @override
- void sendNotification(Notification notification) {
- final json = notification.toJson();
- _sendPort.send(json);
- }
-
- @override
- void sendResponse(Response response) {
- final json = response.toJson();
- _sendPort.send(json);
- }
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin/my_server_plugin.dart b/packages/custom_lint/lib/src/analyzer_plugin/my_server_plugin.dart
deleted file mode 100644
index c7357891..00000000
--- a/packages/custom_lint/lib/src/analyzer_plugin/my_server_plugin.dart
+++ /dev/null
@@ -1,291 +0,0 @@
-// 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 'dart:async';
-
-import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/file_system/overlay_file_system.dart';
-import 'package:analyzer/file_system/physical_file_system.dart';
-import 'package:analyzer_plugin/channel/channel.dart';
-import 'package:analyzer_plugin/protocol/protocol.dart';
-import 'package:analyzer_plugin/protocol/protocol_common.dart';
-import 'package:analyzer_plugin/protocol/protocol_constants.dart';
-import 'package:analyzer_plugin/protocol/protocol_generated.dart';
-
-// ignore: implementation_imports
-import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'
- show ResponseResult;
-import 'package:pub_semver/pub_semver.dart';
-
-import '../log.dart';
-
-/// The abstract superclass of any class implementing a plugin for the analysis
-/// server.
-///
-/// Clients may not implement or mix-in this class, but are expected to extend
-/// it.
-abstract class MyServerPlugin {
- /// Initialize a newly created analysis server plugin. If a resource [provider]
- /// is given, then it will be used to access the file system. Otherwise a
- /// resource provider that accesses the physical file system will be used.
- MyServerPlugin(ResourceProvider? provider)
- : resourceProvider = OverlayResourceProvider(
- provider ?? PhysicalResourceProvider.INSTANCE,
- );
-
- /// A megabyte.
- static const int M = 1024 * 1024;
-
- /// The communication channel being used to communicate with the analysis
- /// server.
- late PluginCommunicationChannel _channel;
-
- /// The resource provider used to access the file system.
- final OverlayResourceProvider resourceProvider;
-
- /// Return the communication channel being used to communicate with the
- /// analysis server, or `null` if the plugin has not been started.
- PluginCommunicationChannel get channel => _channel;
-
- /// Return the user visible information about how to contact the plugin authors
- /// with any problems that are found, or `null` if there is no contact info.
- String? get contactInfo => null;
-
- /// Return a list of glob patterns selecting the files that this plugin is
- /// interested in analyzing.
- List get fileGlobsToAnalyze;
-
- /// Return the user visible name of this plugin.
- String get name;
-
- /// Return the version number of the plugin spec required by this plugin,
- /// encoded as a string.
- String get version;
-
- /// Handle an 'analysis.getNavigation' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleAnalysisGetNavigation(
- AnalysisGetNavigationParams params,
- );
-
- /// Handle an 'analysis.handleWatchEvents' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleAnalysisHandleWatchEvents(
- AnalysisHandleWatchEventsParams parameters,
- );
-
- /// Handle an 'analysis.setContextRoots' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleAnalysisSetContextRoots(
- AnalysisSetContextRootsParams parameters,
- );
-
- /// Handle an 'analysis.setPriorityFiles' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleAnalysisSetPriorityFiles(
- AnalysisSetPriorityFilesParams parameters,
- );
-
- /// Handle an 'analysis.setSubscriptions' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleAnalysisSetSubscriptions(
- AnalysisSetSubscriptionsParams parameters,
- );
-
- /// Handle an 'analysis.updateContent' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleAnalysisUpdateContent(
- AnalysisUpdateContentParams parameters,
- );
-
- /// Handle a 'completion.getSuggestions' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleCompletionGetSuggestions(
- CompletionGetSuggestionsParams parameters,
- );
-
- /// Handle an 'edit.getAssists' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleEditGetAssists(
- EditGetAssistsParams parameters,
- );
-
- /// Handle an 'edit.getAvailableRefactorings' request. Subclasses that override
- /// this method in order to participate in refactorings must also override the
- /// method [handleEditGetRefactoring].
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr
- handleEditGetAvailableRefactorings(
- EditGetAvailableRefactoringsParams parameters,
- );
-
- /// Handle an 'edit.getFixes' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleEditGetFixes(
- EditGetFixesParams parameters,
- );
-
- /// Handle an 'edit.getRefactoring' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleEditGetRefactoring(
- EditGetRefactoringParams parameters,
- );
-
- /// Handle a 'kythe.getKytheEntries' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handleKytheGetKytheEntries(
- KytheGetKytheEntriesParams parameters,
- );
-
- /// Handle a 'plugin.shutdown' request. Subclasses can override this method to
- /// perform any required clean-up, but cannot prevent the plugin from shutting
- /// down.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handlePluginShutdown(
- PluginShutdownParams parameters,
- ) async {
- return PluginShutdownResult();
- }
-
- /// Handle a 'plugin.versionCheck' request.
- ///
- /// Throw a [RequestFailure] if the request could not be handled.
- FutureOr handlePluginVersionCheck(
- PluginVersionCheckParams parameters,
- );
-
- /// Return `true` if this plugin is compatible with an analysis server that is
- /// using the given version of the plugin API.
- bool isCompatibleWith(Version serverVersion) =>
- serverVersion <= Version.parse(version);
-
- /// The method that is called when the analysis server closes the communication
- /// channel. This method will not be invoked under normal conditions because
- /// the server will send a shutdown request and the plugin will stop listening
- /// to the channel before the server closes the channel.
- void onDone() {}
-
- /// The method that is called when an error has occurred in the analysis
- /// server. This method will not be invoked under normal conditions.
- void onError(Object exception, StackTrace stackTrace) {}
-
- /// Start this plugin by listening to the given communication [channel].
- void start(PluginCommunicationChannel channel) {
- _channel = channel;
- _channel.listen(_onRequest, onError: onError, onDone: onDone);
- }
-
- /// Compute the response that should be returned for the given [request], or
- /// `null` if the response has already been sent.
- Future _getResponse(Request request, int requestTime) async {
- ResponseResult? result;
- switch (request.method) {
- case ANALYSIS_REQUEST_GET_NAVIGATION:
- final params = AnalysisGetNavigationParams.fromRequest(request);
- result = await handleAnalysisGetNavigation(params);
- break;
- case ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS:
- final params = AnalysisHandleWatchEventsParams.fromRequest(request);
- result = await handleAnalysisHandleWatchEvents(params);
- break;
- case ANALYSIS_REQUEST_SET_CONTEXT_ROOTS:
- final params = AnalysisSetContextRootsParams.fromRequest(request);
- result = await handleAnalysisSetContextRoots(params);
- break;
- case ANALYSIS_REQUEST_SET_PRIORITY_FILES:
- final params = AnalysisSetPriorityFilesParams.fromRequest(request);
- result = await handleAnalysisSetPriorityFiles(params);
- break;
- case ANALYSIS_REQUEST_SET_SUBSCRIPTIONS:
- final params = AnalysisSetSubscriptionsParams.fromRequest(request);
- result = await handleAnalysisSetSubscriptions(params);
- break;
- case ANALYSIS_REQUEST_UPDATE_CONTENT:
- final params = AnalysisUpdateContentParams.fromRequest(request);
- result = await handleAnalysisUpdateContent(params);
- break;
- case COMPLETION_REQUEST_GET_SUGGESTIONS:
- final params = CompletionGetSuggestionsParams.fromRequest(request);
- result = await handleCompletionGetSuggestions(params);
- break;
- case EDIT_REQUEST_GET_ASSISTS:
- final params = EditGetAssistsParams.fromRequest(request);
- result = await handleEditGetAssists(params);
- break;
- case EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS:
- final params = EditGetAvailableRefactoringsParams.fromRequest(request);
- result = await handleEditGetAvailableRefactorings(params);
- break;
- case EDIT_REQUEST_GET_FIXES:
- final params = EditGetFixesParams.fromRequest(request);
- result = await handleEditGetFixes(params);
- break;
- case EDIT_REQUEST_GET_REFACTORING:
- final params = EditGetRefactoringParams.fromRequest(request);
- result = await handleEditGetRefactoring(params);
- break;
- case KYTHE_REQUEST_GET_KYTHE_ENTRIES:
- final params = KytheGetKytheEntriesParams.fromRequest(request);
- result = await handleKytheGetKytheEntries(params);
- break;
- case PLUGIN_REQUEST_SHUTDOWN:
- final params = PluginShutdownParams();
- result = await handlePluginShutdown(params);
- _channel.sendResponse(result.toResponse(request.id, requestTime));
- _channel.close();
- return null;
- case PLUGIN_REQUEST_VERSION_CHECK:
- final params = PluginVersionCheckParams.fromRequest(request);
- result = await handlePluginVersionCheck(params);
- break;
- }
- if (result == null) {
- return Response(
- request.id,
- requestTime,
- error: RequestErrorFactory.unknownRequest(request.method),
- );
- }
- return result.toResponse(request.id, requestTime);
- }
-
- /// The method that is called when a [request] is received from the analysis
- /// server.
- Future _onRequest(Request request) async {
- final requestTime = DateTime.now().millisecondsSinceEpoch;
- final id = request.id;
- Response? response;
- try {
- response = await _getResponse(request, requestTime);
- } on RequestFailure catch (exception) {
- response = Response(id, requestTime, error: exception.error);
- } catch (exception, stackTrace) {
- response = Response(
- id,
- requestTime,
- error: RequestError(
- RequestErrorCode.PLUGIN_ERROR,
- exception.toString(),
- stackTrace: stackTrace.toString(),
- ),
- );
- }
- if (response != null) {
- _channel.sendResponse(response);
- }
- }
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin/plugin_link.dart b/packages/custom_lint/lib/src/analyzer_plugin/plugin_link.dart
deleted file mode 100644
index 57043229..00000000
--- a/packages/custom_lint/lib/src/analyzer_plugin/plugin_link.dart
+++ /dev/null
@@ -1,139 +0,0 @@
-import 'dart:async';
-import 'dart:isolate';
-
-import 'package:path/path.dart' as p;
-import 'package:analyzer/file_system/file_system.dart' as analyzer;
-import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
-import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
-import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
-import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin
- show RequestParams;
-
-import '../../protocol.dart';
-import '../log.dart';
-
-class PluginLink {
- PluginLink._(
- this._isolate,
- this._sendPort,
- this._responsesController,
- this._notificationsController,
- this._messagesController,
- this._errorController,
- this._receivePort,
- this.key,
- );
-
- factory PluginLink.spawn(Uri pluginRootUri) {
- // TODO configure that through build.yaml-like file
- final mainPath = Uri.file(
- p.join(pluginRootUri.toFilePath(), 'lib', 'main.dart'),
- );
-
- final receivePort = ReceivePort();
-
- final isolate = Isolate.spawnUri(
- mainPath,
- const [],
- receivePort.sendPort,
- automaticPackageResolution: true,
- );
-
-// // TODO do we ca re about killing isolates before _listenIsolate completes?
-
- final errors = StreamController.broadcast();
- final messages = StreamController.broadcast();
- final responses = StreamController.broadcast();
- final notifications = StreamController.broadcast();
- final sendPortCompleter = Completer();
-
- // TODO close subscribption
- receivePort.listen(
- (Object? obj) {
- if (obj is SendPort) {
- sendPortCompleter.complete(obj);
- return;
- }
-
- try {
- final json = Map.from(obj! as Map);
-
- if (json.containsKey(plugin.Notification.EVENT)) {
- final notification = plugin.Notification.fromJson(json);
-
- switch (json[plugin.Notification.EVENT]) {
- case PrintParams.key:
- final print = PrintParams.fromNotification(notification);
- messages.add(print);
- break;
- case 'plugin.error':
- final error =
- plugin.PluginErrorParams.fromNotification(notification);
- errors.add(error);
- break;
- default:
- notifications.add(notification);
- }
- } else {
- final response = plugin.Response.fromJson(json);
- responses.add(response);
- }
- } catch (err, stack) {
- log('failed to decode message $obj with:\n$err\n$stack');
- // TODO handle
- }
- },
- // TODO handle errors
- onDone: () {
- errors.close();
- messages.close();
- responses.close();
- notifications.close();
- },
- );
-
- return PluginLink._(
- isolate,
- sendPortCompleter.future,
- responses,
- notifications,
- messages,
- errors,
- receivePort,
- pluginRootUri,
- );
- }
-
- final Uri key;
- final Future _isolate;
- final Future _sendPort;
- final ReceivePort _receivePort;
- final contextRoots = {};
- final lintsForLibrary = {};
-
- final StreamController _messagesController;
- Stream get messages => _messagesController.stream;
-
- final StreamController _errorController;
- Stream get error => _errorController.stream;
-
- final StreamController _responsesController;
- Stream get responses => _responsesController.stream;
-
- final StreamController _notificationsController;
- Stream get notifications =>
- _notificationsController.stream;
-
- void send(Map json) {
- _sendPort.then((value) => value.send(json));
- }
-
- Future close() async {
- _receivePort.close();
- _messagesController.close();
- _errorController.close();
- _notificationsController.close();
- _responsesController.close();
- return _isolate.then((i) => i.kill());
- }
-}
diff --git a/packages/custom_lint/lib/src/analyzer_plugin_starter.dart b/packages/custom_lint/lib/src/analyzer_plugin_starter.dart
new file mode 100644
index 00000000..ad669e26
--- /dev/null
+++ b/packages/custom_lint/lib/src/analyzer_plugin_starter.dart
@@ -0,0 +1,27 @@
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:ci/ci.dart' as ci;
+
+import 'plugin_delegate.dart';
+import 'v2/custom_lint_analyzer_plugin.dart';
+
+/// Connects custom_lint to the analyzer server using the analyzer_plugin protocol
+Future start(Iterable _, SendPort sendPort) async {
+ final isInCI = ci.isCI;
+
+ await CustomLintServer.start(
+ sendPort: sendPort,
+ includeBuiltInLints: true,
+ // The IDE client should write to files, as what's visible in the editor
+ // may not be the same as what's on disk.
+ fix: false,
+ // "start" may be run by `dart analyze`, in which case we don't want to
+ // enable watch mode. There's no way to detect this, but this only matters
+ // in the CI. So we disable watch mode if we detect that we're in CI.
+ // TODO enable hot-restart only if running plugin from source (excluding pub cache)
+ watchMode: isInCI ? false : null,
+ delegate: AnalyzerPluginCustomLintDelegate(),
+ workingDirectory: Directory.current,
+ );
+}
diff --git a/packages/custom_lint/lib/src/analyzer_utils/analyzer_utils.dart b/packages/custom_lint/lib/src/analyzer_utils/analyzer_utils.dart
new file mode 100644
index 00000000..2bebab27
--- /dev/null
+++ b/packages/custom_lint/lib/src/analyzer_utils/analyzer_utils.dart
@@ -0,0 +1,33 @@
+import 'package:analyzer/file_system/file_system.dart';
+// ignore: implementation_imports, not exported
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
+// ignore: implementation_imports, not exported
+import 'package:analyzer/src/dart/analysis/file_byte_store.dart';
+
+/// Adds [createByteStore].
+extension CreateByteStore on ResourceProvider {
+ /// Obtains the location of a [ByteStore].
+ String getByteStorePath(String pluginID) {
+ final stateLocation = getStateLocation(pluginID);
+
+ if (stateLocation == null) {
+ throw StateError('Failed to obtain the byte store path');
+ }
+
+ return stateLocation.path;
+ }
+
+ /// If the state location can be accessed, return the file byte store,
+ /// otherwise return the memory byte store.
+ ByteStore createByteStore(String pluginID) {
+ const M = 1024 * 1024;
+
+ return MemoryCachingByteStore(
+ FileByteStore(
+ getByteStorePath(pluginID),
+ tempNameSuffix: DateTime.now().millisecondsSinceEpoch.toString(),
+ ),
+ 64 * M,
+ );
+ }
+}
diff --git a/packages/custom_lint/lib/src/async_operation.dart b/packages/custom_lint/lib/src/async_operation.dart
new file mode 100644
index 00000000..6eebb40f
--- /dev/null
+++ b/packages/custom_lint/lib/src/async_operation.dart
@@ -0,0 +1,79 @@
+import 'dart:async';
+
+/// An extension on [Stream] that adds a [safeFirst] method.
+extension StreamFirst on Stream {
+ /// A fork of [first] meant to be used instead of [first], to possibly override
+ /// it during debugging to provide more information.
+ Future get safeFirst => first;
+}
+
+/// A class for awaiting multiple async operations at once.
+///
+/// See [wait].
+class PendingOperation {
+ final _pendingOperations = >[];
+
+ /// Register an async operation to be awaited.
+ Future run(Future Function() cb) async {
+ final future = cb();
+
+ _pendingOperations.add(future);
+ try {
+ return await future;
+ } finally {
+ _pendingOperations.remove(future);
+ }
+ }
+
+ /// Waits for all operations registered in [run].
+ ///
+ /// If during the wait new async operations are registered, they will be
+ /// awaited too.
+ Future wait() async {
+ /// Wait for all pending operations to complete and check that no new
+ /// operations are queued for a few consecutive frames.
+ while (_pendingOperations.isNotEmpty) {
+ await Future.wait(_pendingOperations.toList())
+ // Catches errors to make sure that errors inside operations don't
+ // abort the "wait" early
+ .then((value) => null, onError: (_) {});
+ }
+ }
+}
+
+/// Workaround to a limitation in [runZonedGuarded] that states the following:
+///
+/// > The zone will always be an error-zone ([Zone.errorZone]), so returning a
+/// > future created inside the zone, and waiting for it outside of the zone,
+/// > will risk the future not being seen to complete.
+///
+/// This function solves the issue by creating a [Completer] outside of
+/// [runZonedGuarded] and completing it inside the zone. This way, the future
+/// is created outside of the zone and can safely be awaited.
+Future asyncRunZonedGuarded(
+ FutureOr Function() body,
+ void Function(Object error, StackTrace stack) onError, {
+ Map