├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ ├── checker.yml │ ├── pub_dry_run.yml │ ├── pub_publish.yml │ └── pub_publish_manually.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-ZH.md ├── README.md ├── analysis_command.png ├── analysis_options.yaml ├── bin ├── arg │ ├── arg.dart │ ├── arg_parser.dart │ ├── args.dart │ ├── clear_cache.dart │ ├── example.dart │ ├── help.dart │ └── pre_commit.dart └── main.dart ├── command_palette.png ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── assets │ └── app_en.json ├── custom_lint │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ └── custom_lint.dart │ ├── pubspec.yaml │ └── tools │ │ └── analyzer_plugin │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── bin │ │ ├── debug.dart │ │ ├── plugin.dart │ │ └── pre_commit.dart │ │ ├── lib │ │ └── custom_lint_analyzer_plugin.dart │ │ └── pubspec.yaml ├── flutter_package │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ ├── extension │ │ │ └── int.dart │ │ ├── flutter_package.dart │ │ ├── test.dart │ │ └── test1.dart │ └── pubspec.yaml ├── lib │ ├── class_prefix.dart │ ├── comments.dart │ ├── dispose.dart │ ├── exclude │ │ └── test.dart │ ├── extension │ │ ├── int.dart │ │ └── int_copy.dart │ ├── include │ │ └── test.dart │ ├── main.dart │ ├── singleton.dart │ ├── test.dart │ ├── test_part.dart │ └── trailing_comma.dart ├── pre-commit └── pubspec.yaml ├── lib ├── candies_analyzer_plugin.dart └── src │ ├── ansi_code.dart │ ├── ast_visitor.dart │ ├── completion │ ├── completion_mixin.dart │ ├── contributors │ │ └── extension_member_contributor.dart │ └── display_string_builder.dart │ ├── config.dart │ ├── error │ ├── error │ │ ├── dart.dart │ │ ├── generic.dart │ │ └── yaml.dart │ ├── lints │ │ ├── dart │ │ │ ├── call_super_dispose.dart │ │ │ ├── dart_lint.dart │ │ │ ├── good_doc_comments.dart │ │ │ ├── perfer_class_prefix.dart │ │ │ ├── perfer_doc_comments.dart │ │ │ ├── perfer_singleton.dart │ │ │ ├── prefer_asset_const.dart │ │ │ ├── prefer_named_routes.dart │ │ │ ├── prefer_safe_set_state.dart │ │ │ ├── prefer_trailing_comma.dart │ │ │ ├── unused_code.dart │ │ │ ├── unused_file.dart │ │ │ └── util │ │ │ │ ├── analyzer.dart │ │ │ │ ├── ast.dart │ │ │ │ └── utils.dart │ │ ├── generic_lint.dart │ │ ├── lint.dart │ │ └── yaml_lint.dart │ └── plugin │ │ ├── dart_mixin.dart │ │ ├── generic_mixin.dart │ │ └── yaml_mixin.dart │ ├── extension.dart │ ├── ignore_info.dart │ ├── log.dart │ ├── plugin.dart │ ├── plugin_base.dart │ └── plugin_starter.dart ├── pubspec.yaml ├── test └── candies_analyzer_plugin_test.dart └── tools └── analyzer_plugin ├── .gitignore ├── CHANGELOG.md ├── README.md ├── analysis_options.yaml ├── bin └── plugin.dart └── pubspec.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: zmtzawqlp 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | custom: http://zmtzawqlp.gitee.io/my_images/images/qrcode.png 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: true -------------------------------------------------------------------------------- /.github/workflows/checker.yml: -------------------------------------------------------------------------------- 1 | name: No Free usage issue checker 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Check issue actor 15 | uses: ./ 16 | with: 17 | repo: $GITHUB_REPOSITORY 18 | user: $GITHUB_ACTOR 19 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/pub_dry_run.yml: -------------------------------------------------------------------------------- 1 | name: Pub Publish dry run 2 | 3 | on: [push] 4 | 5 | jobs: 6 | publish: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | - name: Publish 14 | uses: sakebook/actions-flutter-pub-publisher@v1.3.0 15 | with: 16 | credential: ${{ secrets.CREDENTIAL_JSON }} 17 | flutter_package: true 18 | skip_test: true 19 | dry_run: true 20 | -------------------------------------------------------------------------------- /.github/workflows/pub_publish.yml: -------------------------------------------------------------------------------- 1 | name: Pub Publish plugin 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v1 15 | - name: Publish 16 | uses: sakebook/actions-flutter-pub-publisher@v1.3.0 17 | with: 18 | credential: ${{ secrets.CREDENTIAL_JSON }} 19 | flutter_package: true 20 | skip_test: true 21 | dry_run: false 22 | -------------------------------------------------------------------------------- /.github/workflows/pub_publish_manually.yml: -------------------------------------------------------------------------------- 1 | name: Pub Publish plugin 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | publish: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | - name: Publish 14 | uses: sakebook/actions-flutter-pub-publisher@v1.3.0 15 | with: 16 | credential: ${{ secrets.CREDENTIAL_JSON }} 17 | flutter_package: true 18 | skip_test: true 19 | dry_run: false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | pubspec.lock 8 | .idea -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 5.0.2 2 | 3 | * fix error at `pre_commit.dart` in example 4 | ## 5.0.1 5 | 6 | * `pre_commit.dart` add onlyAnalyzeChangedFiles judgement 7 | 8 | ## 5.0.0 9 | 10 | * breaking change: use new commands. 11 | * add `--[no-]pre-commit` to create a pre-commit shell in .git/hook 12 | * add `--[no-]clear-cache` to clear cache under .dartServer/.plugin_manager/ . 13 | * add `--example` to create a analyzer_plugin example. 14 | * add `pre_commit.dart` to check code before commit. 15 | 16 | ## 4.0.2 17 | 18 | * support to cache errors into file. 19 | 20 | ## 4.0.1 21 | 22 | * make candies_analyzer_plugin as plugin, you can simply use it now. 23 | 24 | ## 4.0.0 25 | 26 | * add `good_doc_comments` 27 | * add `prefer_trailing_comma` 28 | * support to fix errors where possible in file, see `prefer_trailing_comma`. 29 | * breaking change: 30 | 1. change method `getDartFixes(ResolvedUnitResult resolvedUnitResult,AstNode astNode,)` to `getDartFixes(DartAnalysisError error,CandiesAnalyzerPluginConfig config,)`, you can get resolvedUnitResult and astNode from error, and get cache errors from config. 31 | 2. add CandiesAnalyzerPluginConfig config for `getYamlFixes` and `getGenericFixes`. 32 | 33 | ## 3.3.3 34 | 35 | * add prefer_singleton 36 | 37 | ## 3.3.2 38 | 39 | * fix `unused_file` for part file 40 | 41 | ## 3.3.1 42 | 43 | * fix `unused_file` skip import file 44 | 45 | ## 3.3.0 46 | 47 | * add `unused_file` 48 | 49 | ## 3.2.0 50 | 51 | * add [beforeSendAnalysisErrors] method for [CandiesAnalyzerPlugin], you can edit AnalysisError before to be send. 52 | * add [showAnalysisErrorWithGitAuthor] property for [CandiesAnalyzerPlugin], support to add git author into error message. 53 | 54 | ## 3.1.2 55 | 56 | * `perfer_doc_comments` add method [isValidDocumentationComment] 57 | 58 | ## 3.1.1 59 | 60 | * `perfer_doc_comments` add check FunctionDeclaration 61 | 62 | ## 3.1.0 63 | 64 | * add `perfer_doc_comments`, it's same like `public_member_api_docs` but we can ignore lint or ignore file by override [ignoreLint] and [ignoreFile] and you can override [isPrivate] and [inPrivateMember] to check private member. 65 | * add [ignoreLint] and [ignoreFile] methods for [DartLint], override they base on your rule. 66 | * add [astVisitor] for [DartLint], you can custom astVisitor for one lint. 67 | 68 | ## 3.0.0 69 | 70 | * rename `candies_lints` to `candies_analyzer_plugin`. 71 | * support to get suggestion and auto import for extension member. 72 | * breaking change some classes are refactored. 73 | 74 | ## 2.0.3 75 | 76 | * remove dartLints.isEmpty in analyzeFile and handleEditGetFixes methods. 77 | 78 | ## 2.0.2 79 | 80 | * add `must_call_super_dispose` and `end_call_super_dispose` lints. 81 | 82 | ## 2.0.1 83 | 84 | * add command `clear_cache` to clear plugin_manager cache. 85 | 86 | ## 2.0.0 87 | 88 | * refactor code to support dart, yaml, generic file lint. 89 | * support yaml lint 90 | * support generic lint 91 | 92 | ## 1.0.2 93 | 94 | * rename prefer_safe_set_state.dart 95 | 96 | ## 1.0.1 97 | 98 | * Update method to debug 99 | 100 | ## 1.0.0 101 | 102 | * Initial version. 103 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zmtzawqlp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zmtzawqlp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /analysis_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/candies_analyzer_plugin/72e408279a770ebf0277abf3db296724829d69a5/analysis_command.png -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | analyzer: 15 | strong-mode: 16 | implicit-casts: false 17 | implicit-dynamic: false 18 | # enable-experiment: 19 | # - extension-methods 20 | errors: 21 | # treat missing required parameters as a warning (not a hint) 22 | missing_required_param: warning 23 | # treat missing returns as a warning (not a hint) 24 | missing_return: warning 25 | # allow having TODOs in the code 26 | todo: ignore 27 | # Ignore analyzer hints for updating pubspecs when using Future or 28 | # Stream and not importing dart:async 29 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 30 | sdk_version_async_exported_from_core: ignore 31 | exclude: 32 | 33 | linter: 34 | rules: 35 | # these rules are documented on and in the same order as 36 | # the Dart Lint rules page to make maintenance easier 37 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 38 | - always_declare_return_types 39 | - always_put_control_body_on_new_line 40 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 41 | - always_require_non_null_named_parameters 42 | - always_specify_types 43 | - annotate_overrides 44 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 45 | # - avoid_as # required for implicit-casts: true 46 | - avoid_bool_literals_in_conditional_expressions 47 | # - avoid_catches_without_on_clauses # we do this commonly 48 | # - avoid_catching_errors # we do this commonly 49 | # - avoid_classes_with_only_static_members 50 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 51 | - avoid_empty_else 52 | - avoid_equals_and_hash_code_on_mutable_classes 53 | - avoid_field_initializers_in_const_classes 54 | - avoid_function_literals_in_foreach_calls 55 | # - avoid_implementing_value_types # not yet tested 56 | - avoid_init_to_null 57 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 58 | - avoid_null_checks_in_equality_operators 59 | # - avoid_positional_boolean_parameters # not yet tested 60 | # - avoid_print # not yet tested 61 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 62 | # - avoid_redundant_argument_values # not yet tested 63 | - avoid_relative_lib_imports 64 | - avoid_renaming_method_parameters 65 | - avoid_return_types_on_setters 66 | # - avoid_returning_null # there are plenty of valid reasons to return null 67 | # - avoid_returning_null_for_future # not yet tested 68 | - avoid_returning_null_for_void 69 | # - avoid_returning_this # there are plenty of valid reasons to return this 70 | # - avoid_setters_without_getters # not yet tested 71 | # - avoid_shadowing_type_parameters # not yet tested 72 | - avoid_single_cascade_in_expression_statements 73 | - avoid_slow_async_io 74 | - avoid_types_as_parameter_names 75 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 76 | # - avoid_unnecessary_containers # not yet tested 77 | - avoid_unused_constructor_parameters 78 | - avoid_void_async 79 | # - avoid_web_libraries_in_flutter # not yet tested 80 | - await_only_futures 81 | - camel_case_extensions 82 | - camel_case_types 83 | - cancel_subscriptions 84 | # - cascade_invocations # not yet tested 85 | # - close_sinks # not reliable enough 86 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 87 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 88 | - control_flow_in_finally 89 | # - curly_braces_in_flow_control_structures # not yet tested 90 | # - diagnostic_describe_all_properties # not yet tested 91 | # - directives_ordering 92 | - empty_catches 93 | - empty_constructor_bodies 94 | - empty_statements 95 | # - file_names # not yet tested 96 | - flutter_style_todos 97 | - hash_and_equals 98 | - implementation_imports 99 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 100 | - iterable_contains_unrelated_type 101 | # - join_return_with_assignment # not yet tested 102 | - library_names 103 | - library_prefixes 104 | # - lines_longer_than_80_chars # not yet tested 105 | - list_remove_unrelated_type 106 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 107 | # - missing_whitespace_between_adjacent_strings # not yet tested 108 | - no_adjacent_strings_in_list 109 | - no_duplicate_case_values 110 | # - no_logic_in_create_state # not yet tested 111 | # - no_runtimeType_toString # not yet tested 112 | - non_constant_identifier_names 113 | # - null_closures # not yet tested 114 | # - omit_local_variable_types # opposite of always_specify_types 115 | # - one_member_abstracts # too many false positives 116 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 117 | - overridden_fields 118 | - package_api_docs 119 | - package_names 120 | - package_prefixed_library_names 121 | # - parameter_assignments # we do this commonly 122 | - prefer_adjacent_string_concatenation 123 | - prefer_asserts_in_initializer_lists 124 | # - prefer_asserts_with_message # not yet tested 125 | - prefer_collection_literals 126 | - prefer_conditional_assignment 127 | - prefer_const_constructors 128 | - prefer_const_constructors_in_immutables 129 | - prefer_const_declarations 130 | - prefer_const_literals_to_create_immutables 131 | # - prefer_constructors_over_static_methods # not yet tested 132 | - prefer_contains 133 | # - prefer_double_quotes # opposite of prefer_single_quotes 134 | - prefer_equal_for_default_values 135 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 136 | - prefer_final_fields 137 | - prefer_final_in_for_each 138 | - prefer_final_locals 139 | - prefer_for_elements_to_map_fromIterable 140 | - prefer_foreach 141 | # - prefer_function_declarations_over_variables # not yet tested 142 | - prefer_generic_function_type_aliases 143 | - prefer_if_elements_to_conditional_expressions 144 | - prefer_if_null_operators 145 | - prefer_initializing_formals 146 | - prefer_inlined_adds 147 | # - prefer_int_literals # not yet tested 148 | # - prefer_interpolation_to_compose_strings # not yet tested 149 | - prefer_is_empty 150 | - prefer_is_not_empty 151 | - prefer_is_not_operator 152 | - prefer_iterable_whereType 153 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 154 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 155 | # - prefer_relative_imports # not yet tested 156 | - prefer_single_quotes 157 | - prefer_spread_collections 158 | - prefer_typing_uninitialized_variables 159 | - prefer_void_to_null 160 | # - provide_deprecation_message # not yet tested 161 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 162 | - recursive_getters 163 | - slash_for_doc_comments 164 | # - sort_child_properties_last # not yet tested 165 | - sort_constructors_first 166 | # - sort_pub_dependencies 167 | - sort_unnamed_constructors_first 168 | - test_types_in_equals 169 | - throw_in_finally 170 | # - type_annotate_public_apis # subset of always_specify_types 171 | - type_init_formals 172 | # - unawaited_futures # too many false positives 173 | # - unnecessary_await_in_return # not yet tested 174 | - unnecessary_brace_in_string_interps 175 | - unnecessary_const 176 | # - unnecessary_final # conflicts with prefer_final_locals 177 | - unnecessary_getters_setters 178 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 179 | - unnecessary_new 180 | - unnecessary_null_aware_assignments 181 | - unnecessary_null_in_if_null_operators 182 | - unnecessary_overrides 183 | - unnecessary_parenthesis 184 | - unnecessary_statements 185 | #- unnecessary_string_interpolations 186 | - unnecessary_this 187 | - unrelated_type_equality_checks 188 | # - unsafe_html # not yet tested 189 | - use_full_hex_values_for_flutter_colors 190 | # - use_function_type_syntax_for_parameters # not yet tested 191 | # - use_key_in_widget_constructors # not yet tested 192 | - use_rethrow_when_possible 193 | # - use_setters_to_change_properties # not yet tested 194 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 195 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 196 | - valid_regexps 197 | - void_checks 198 | -------------------------------------------------------------------------------- /bin/arg/arg.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:io/ansi.dart'; 5 | 6 | import 'arg_parser.dart'; 7 | 8 | abstract class Argument { 9 | Argument() { 10 | if (false is T) { 11 | parser.addFlag(name, 12 | abbr: abbr, help: help, defaultsTo: defaultsTo as bool?); 13 | } else if ('' is T) { 14 | parser.addOption(name, 15 | abbr: abbr, help: help, defaultsTo: defaultsTo as String?); 16 | } else if ([] is T) { 17 | parser.addMultiOption( 18 | name, 19 | abbr: abbr, 20 | help: help, 21 | defaultsTo: defaultsTo as List?, 22 | ); 23 | } else { 24 | // TODO(zmtzawqlp): not implement for now. 25 | throw Exception('not implement fill method'); 26 | } 27 | } 28 | 29 | /// The name of the option that the user passes as an argument. 30 | String get name; 31 | 32 | /// A single-character string that can be used as a shorthand for this option. 33 | /// 34 | /// For example, `abbr: "a"` will allow the user to pass `-a value` or 35 | /// `-avalue`. 36 | String? get abbr; 37 | 38 | /// A description of this option. 39 | String get help; 40 | 41 | /// The value this option will have if the user doesn't explicitly pass it in 42 | T? get defaultsTo; 43 | 44 | /// The value this option 45 | T? get value { 46 | if (argResults.wasParsed(name)) { 47 | return argResults[name] as T?; 48 | } 49 | return defaultsTo; 50 | } 51 | 52 | void run(); 53 | } 54 | 55 | String processRun({ 56 | required String executable, 57 | String? arguments, 58 | bool runInShell = false, 59 | String? workingDirectory, 60 | List? argumentsList, 61 | Encoding? stdoutEncoding = systemEncoding, 62 | Encoding? stderrEncoding = systemEncoding, 63 | bool printInfo = true, 64 | }) { 65 | final List temp = []; 66 | 67 | if (arguments != null) { 68 | temp.addAll( 69 | arguments.split(' ')..removeWhere((String x) => x.trim() == '')); 70 | } 71 | 72 | if (argumentsList != null) { 73 | temp.addAll(argumentsList); 74 | } 75 | if (printInfo) { 76 | print(yellow.wrap('$executable $temp')); 77 | } 78 | final ProcessResult result = Process.runSync( 79 | executable, 80 | temp, 81 | runInShell: runInShell, 82 | workingDirectory: workingDirectory, 83 | stdoutEncoding: stdoutEncoding, 84 | stderrEncoding: stderrEncoding, 85 | ); 86 | if (result.exitCode != 0) { 87 | throw Exception(result.stderr); 88 | } 89 | 90 | final String stdout = result.stdout.toString(); 91 | if (printInfo) { 92 | print(green.wrap('stdout: $stdout\n')); 93 | } 94 | 95 | return stdout; 96 | } 97 | -------------------------------------------------------------------------------- /bin/arg/arg_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:args/args.dart'; 2 | 3 | import 'args.dart'; 4 | 5 | ArgParser parser = ArgParser(); 6 | late ArgResults argResults; 7 | void parseArgs(List args) { 8 | Args(); 9 | argResults = parser.parse(args); 10 | } 11 | -------------------------------------------------------------------------------- /bin/arg/args.dart: -------------------------------------------------------------------------------- 1 | import 'clear_cache.dart'; 2 | import 'help.dart'; 3 | import 'example.dart'; 4 | import 'pre_commit.dart'; 5 | 6 | class Args { 7 | factory Args() => _args ??= Args._(); 8 | 9 | Args._() 10 | : help = Help(), 11 | example = Example(), 12 | preCommit = PreCommit(), 13 | clearCache = ClearCache(); 14 | static Args? _args; 15 | final Help help; 16 | final Example example; 17 | final ClearCache clearCache; 18 | final PreCommit preCommit; 19 | 20 | void run() { 21 | if (clearCache.value ?? false) { 22 | clearCache.run(); 23 | } else if (preCommit.value ?? false) { 24 | preCommit.run(); 25 | } else if (example.value != null) { 26 | example.run(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bin/arg/clear_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:io/ansi.dart'; 4 | 5 | import 'arg.dart'; 6 | import 'package:path/path.dart' as path; 7 | 8 | class ClearCache extends Argument { 9 | @override 10 | String? get abbr => null; 11 | 12 | @override 13 | bool get defaultsTo => false; 14 | 15 | @override 16 | String get help => 'Clear cache under .dartServer/.plugin_manager/'; 17 | 18 | @override 19 | String get name => 'clear-cache'; 20 | 21 | @override 22 | void run() { 23 | String? home; 24 | final Map envVars = Platform.environment; 25 | if (Platform.isMacOS) { 26 | home = envVars['HOME']; 27 | } else if (Platform.isLinux) { 28 | home = envVars['HOME']; 29 | } else if (Platform.isWindows) { 30 | home = envVars['UserProfile']; 31 | } 32 | 33 | if (home != null) { 34 | Directory? directory; 35 | // macos: `/Users/user_name/.dartServer/.plugin_manager/` 36 | // windows: `C:\Users\user_name\AppData\Local\.dartServer\.plugin_manager\` 37 | if (Platform.isMacOS) { 38 | directory = 39 | Directory(path.join(home, '.dartServer', '.plugin_manager')); 40 | } else if (Platform.isLinux) { 41 | directory = 42 | Directory(path.join(home, '.dartServer', '.plugin_manager')); 43 | } else if (Platform.isWindows) { 44 | directory = Directory(path.join( 45 | home, 'AppData', 'Local', '.dartServer', '.plugin_manager')); 46 | } 47 | 48 | if (directory != null && directory.existsSync()) { 49 | print(green.wrap('clear plugin_manager cache successfully!')); 50 | directory.deleteSync(recursive: true); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bin/arg/help.dart: -------------------------------------------------------------------------------- 1 | import 'arg.dart'; 2 | 3 | class Help extends Argument { 4 | @override 5 | String get abbr => 'h'; 6 | 7 | @override 8 | bool get defaultsTo => false; 9 | 10 | @override 11 | String get help => 'Help usage'; 12 | 13 | @override 14 | String get name => 'help'; 15 | 16 | @override 17 | void run() {} 18 | } 19 | -------------------------------------------------------------------------------- /bin/arg/pre_commit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'arg.dart'; 4 | import 'package:path/path.dart' as path; 5 | 6 | class PreCommit extends Argument { 7 | @override 8 | String? get abbr => null; 9 | 10 | @override 11 | bool get defaultsTo => false; 12 | 13 | @override 14 | String get help => 'Create a pre-commit script in .git/hooks'; 15 | 16 | @override 17 | String get name => 'pre-commit'; 18 | 19 | final String preCommitDartFile = 20 | path.join('tools', 'analyzer_plugin', 'bin', 'pre_commit.dart'); 21 | 22 | File? findPreCommitFile(Directory directory) { 23 | for (final FileSystemEntity file in directory.listSync()) { 24 | if (file is Directory && !path.basename(file.path).startsWith('.')) { 25 | final File? dartFile = findPreCommitFile(file); 26 | if (dartFile != null) { 27 | return dartFile; 28 | } 29 | } else if (file is File && file.path.endsWith(preCommitDartFile)) { 30 | return file; 31 | } 32 | } 33 | 34 | return null; 35 | } 36 | 37 | @override 38 | void run() { 39 | final String gitRoot = processRun( 40 | executable: 'git', 41 | arguments: 'rev-parse --show-toplevel', 42 | //runInShell: true, 43 | printInfo: false, 44 | ).trim(); 45 | 46 | final File preCommitSH = 47 | File(path.join(gitRoot, '.git', 'hooks', 'pre-commit')); 48 | 49 | final File? file = findPreCommitFile(Directory(gitRoot)); 50 | if (file != null) { 51 | if (preCommitSH.existsSync()) { 52 | preCommitSH.deleteSync(); 53 | } 54 | preCommitSH.createSync(); 55 | final String source = Directory.current.path; 56 | final File localConfig = File(path.join(source, 'pre-commit')); 57 | String demo = preCommitSHDemo; 58 | if (localConfig.existsSync()) { 59 | demo = localConfig.readAsStringSync(); 60 | } 61 | preCommitSH.writeAsString( 62 | demo 63 | .replaceAll( 64 | '{0}', 65 | source, 66 | ) 67 | .replaceAll('{1}', file.path), 68 | ); 69 | if (Platform.isMacOS || Platform.isLinux) { 70 | processRun( 71 | executable: 'chmod', 72 | arguments: '777 ${preCommitSH.path}', 73 | printInfo: false, 74 | ); 75 | } 76 | print('${preCommitSH.path} has created'); 77 | } else { 78 | print( 79 | 'not find pre_commit.dart, please run \'candies_analyzer_plugin plugin_name\' first.'); 80 | } 81 | } 82 | } 83 | 84 | const String preCommitSHDemo = ''' 85 | #!/bin/sh 86 | 87 | # project path 88 | base_dir="{0}" 89 | 90 | dart format "\$base_dir" 91 | 92 | # pre_commit.dart path 93 | pre_commit="{1}" 94 | 95 | echo "Checking the code before submit..." 96 | echo "Analyzing \$base_dir..." 97 | 98 | info=\$(dart "\$pre_commit" "\$base_dir") 99 | 100 | echo "\$info" 101 | 102 | if [[ -n \$info && \$info != *"No issues found"* ]];then 103 | exit 1 104 | fi 105 | '''; 106 | -------------------------------------------------------------------------------- /bin/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:io/ansi.dart'; 5 | import 'package:path/path.dart' as path; 6 | 7 | import 'arg/arg_parser.dart'; 8 | import 'arg/args.dart'; 9 | import 'arg/clear_cache.dart'; 10 | import 'arg/pre_commit.dart'; 11 | 12 | void main(List args) { 13 | parseArgs(args); 14 | 15 | if (args.isEmpty || Args().help.value!) { 16 | print(green.wrap(parser.usage)); 17 | return; 18 | } 19 | 20 | Args().run(); 21 | } 22 | -------------------------------------------------------------------------------- /command_palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/candies_analyzer_plugin/72e408279a770ebf0277abf3db296724829d69a5/command_palette.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | pubspec.lock -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: f1875d570e39de09040c8f79aa13cc56baab8db1 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 17 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 18 | - platform: android 19 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 20 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 21 | - platform: ios 22 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 23 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 24 | - platform: linux 25 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 26 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 27 | - platform: macos 28 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 29 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 30 | - platform: web 31 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 32 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 33 | - platform: windows 34 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 35 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | analyzer: 12 | errors: 13 | unrecognized_error_code: ignore 14 | #perfer_candies_class_prefix: ignore 15 | # override error severity 16 | perfer_candies_class_prefix: warning 17 | # zmtzawqlp 18 | plugins: 19 | custom_lint 20 | exclude: 21 | - lib/exclude/*.dart 22 | linter: 23 | # The lint rules applied to this project can be customized in the 24 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 25 | # included above or to enable additional rules. A list of all available lints 26 | # and their documentation is published at 27 | # https://dart-lang.github.io/linter/lints/index.html. 28 | # 29 | # Instead of disabling a lint rule for the entire project in the 30 | # section below, it can also be suppressed for a single line of code 31 | # or a specific dart file by using the `// ignore: name_of_lint` and 32 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 33 | # producing the lint. 34 | rules: 35 | #perfer_candies_class_prefix: true # Uncomment to disable the `avoid_print` rule 36 | #prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 37 | #- prefer_single_quotes 38 | #- public_member_api_docs 39 | # Additional information about this file can be found at 40 | # https://dart.dev/guides/language/analysis-options 41 | 42 | # your plugin name 43 | custom_lint: 44 | # if we define this, we only analyze include files 45 | include: 46 | # - lib/include/*.dart -------------------------------------------------------------------------------- /example/assets/app_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "loading_text": "loading", 3 | "loading_button_text": "loading" 4 | } 5 | -------------------------------------------------------------------------------- /example/custom_lint/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | -------------------------------------------------------------------------------- /example/custom_lint/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version. 4 | -------------------------------------------------------------------------------- /example/custom_lint/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /example/custom_lint/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | include: package:lints/recommended.yaml 15 | 16 | # Uncomment the following section to specify additional rules. 17 | 18 | # linter: 19 | # rules: 20 | # - camel_case_types 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** 25 | 26 | # For more information about the core and recommended set of lints, see 27 | # https://dart.dev/go/core-lints 28 | 29 | # For additional information about configuring this file, see 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /example/custom_lint/lib/custom_lint.dart: -------------------------------------------------------------------------------- 1 | /// Support for doing something awesome. 2 | /// 3 | /// More dartdocs go here. 4 | library custom_lint; 5 | 6 | // TODO: Export any libraries intended for clients of this package. 7 | -------------------------------------------------------------------------------- /example/custom_lint/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: custom_lint 2 | description: A starting point for Dart libraries or applications. 3 | version: 1.0.0 4 | # homepage: https://www.example.com 5 | 6 | environment: 7 | sdk: '>=2.17.6 <3.0.0' 8 | 9 | # dependencies: 10 | # path: ^1.8.0 11 | 12 | dev_dependencies: 13 | lints: ^2.0.0 14 | test: ^1.16.0 15 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version. 4 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/README.md: -------------------------------------------------------------------------------- 1 | A sample command-line application with an entrypoint in `bin/`, library code 2 | in `lib/`, and example unit test in `test/`. 3 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | analyzer: 15 | strong-mode: 16 | implicit-casts: false 17 | implicit-dynamic: false 18 | # enable-experiment: 19 | # - extension-methods 20 | errors: 21 | # treat missing required parameters as a warning (not a hint) 22 | missing_required_param: warning 23 | # treat missing returns as a warning (not a hint) 24 | missing_return: warning 25 | # allow having TODOs in the code 26 | todo: ignore 27 | # Ignore analyzer hints for updating pubspecs when using Future or 28 | # Stream and not importing dart:async 29 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 30 | sdk_version_async_exported_from_core: ignore 31 | exclude: 32 | 33 | linter: 34 | rules: 35 | # these rules are documented on and in the same order as 36 | # the Dart Lint rules page to make maintenance easier 37 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 38 | - always_declare_return_types 39 | - always_put_control_body_on_new_line 40 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 41 | - always_require_non_null_named_parameters 42 | - always_specify_types 43 | - annotate_overrides 44 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 45 | # - avoid_as # required for implicit-casts: true 46 | - avoid_bool_literals_in_conditional_expressions 47 | # - avoid_catches_without_on_clauses # we do this commonly 48 | # - avoid_catching_errors # we do this commonly 49 | # - avoid_classes_with_only_static_members 50 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 51 | - avoid_empty_else 52 | - avoid_equals_and_hash_code_on_mutable_classes 53 | - avoid_field_initializers_in_const_classes 54 | - avoid_function_literals_in_foreach_calls 55 | # - avoid_implementing_value_types # not yet tested 56 | - avoid_init_to_null 57 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 58 | - avoid_null_checks_in_equality_operators 59 | # - avoid_positional_boolean_parameters # not yet tested 60 | # - avoid_print # not yet tested 61 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 62 | # - avoid_redundant_argument_values # not yet tested 63 | - avoid_relative_lib_imports 64 | - avoid_renaming_method_parameters 65 | - avoid_return_types_on_setters 66 | # - avoid_returning_null # there are plenty of valid reasons to return null 67 | # - avoid_returning_null_for_future # not yet tested 68 | - avoid_returning_null_for_void 69 | # - avoid_returning_this # there are plenty of valid reasons to return this 70 | # - avoid_setters_without_getters # not yet tested 71 | # - avoid_shadowing_type_parameters # not yet tested 72 | - avoid_single_cascade_in_expression_statements 73 | - avoid_slow_async_io 74 | - avoid_types_as_parameter_names 75 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 76 | # - avoid_unnecessary_containers # not yet tested 77 | - avoid_unused_constructor_parameters 78 | - avoid_void_async 79 | # - avoid_web_libraries_in_flutter # not yet tested 80 | - await_only_futures 81 | - camel_case_extensions 82 | - camel_case_types 83 | - cancel_subscriptions 84 | # - cascade_invocations # not yet tested 85 | # - close_sinks # not reliable enough 86 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 87 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 88 | - control_flow_in_finally 89 | # - curly_braces_in_flow_control_structures # not yet tested 90 | # - diagnostic_describe_all_properties # not yet tested 91 | # - directives_ordering 92 | - empty_catches 93 | - empty_constructor_bodies 94 | - empty_statements 95 | # - file_names # not yet tested 96 | - flutter_style_todos 97 | - hash_and_equals 98 | - implementation_imports 99 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 100 | - iterable_contains_unrelated_type 101 | # - join_return_with_assignment # not yet tested 102 | - library_names 103 | - library_prefixes 104 | # - lines_longer_than_80_chars # not yet tested 105 | - list_remove_unrelated_type 106 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 107 | # - missing_whitespace_between_adjacent_strings # not yet tested 108 | - no_adjacent_strings_in_list 109 | - no_duplicate_case_values 110 | # - no_logic_in_create_state # not yet tested 111 | # - no_runtimeType_toString # not yet tested 112 | - non_constant_identifier_names 113 | # - null_closures # not yet tested 114 | # - omit_local_variable_types # opposite of always_specify_types 115 | # - one_member_abstracts # too many false positives 116 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 117 | - overridden_fields 118 | - package_api_docs 119 | - package_names 120 | - package_prefixed_library_names 121 | # - parameter_assignments # we do this commonly 122 | - prefer_adjacent_string_concatenation 123 | - prefer_asserts_in_initializer_lists 124 | # - prefer_asserts_with_message # not yet tested 125 | - prefer_collection_literals 126 | - prefer_conditional_assignment 127 | - prefer_const_constructors 128 | - prefer_const_constructors_in_immutables 129 | - prefer_const_declarations 130 | - prefer_const_literals_to_create_immutables 131 | # - prefer_constructors_over_static_methods # not yet tested 132 | - prefer_contains 133 | # - prefer_double_quotes # opposite of prefer_single_quotes 134 | - prefer_equal_for_default_values 135 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 136 | - prefer_final_fields 137 | - prefer_final_in_for_each 138 | - prefer_final_locals 139 | - prefer_for_elements_to_map_fromIterable 140 | - prefer_foreach 141 | # - prefer_function_declarations_over_variables # not yet tested 142 | - prefer_generic_function_type_aliases 143 | - prefer_if_elements_to_conditional_expressions 144 | - prefer_if_null_operators 145 | - prefer_initializing_formals 146 | - prefer_inlined_adds 147 | # - prefer_int_literals # not yet tested 148 | # - prefer_interpolation_to_compose_strings # not yet tested 149 | - prefer_is_empty 150 | - prefer_is_not_empty 151 | - prefer_is_not_operator 152 | - prefer_iterable_whereType 153 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 154 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 155 | # - prefer_relative_imports # not yet tested 156 | - prefer_single_quotes 157 | - prefer_spread_collections 158 | - prefer_typing_uninitialized_variables 159 | - prefer_void_to_null 160 | # - provide_deprecation_message # not yet tested 161 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 162 | - recursive_getters 163 | - slash_for_doc_comments 164 | # - sort_child_properties_last # not yet tested 165 | - sort_constructors_first 166 | # - sort_pub_dependencies 167 | - sort_unnamed_constructors_first 168 | - test_types_in_equals 169 | - throw_in_finally 170 | # - type_annotate_public_apis # subset of always_specify_types 171 | - type_init_formals 172 | # - unawaited_futures # too many false positives 173 | # - unnecessary_await_in_return # not yet tested 174 | - unnecessary_brace_in_string_interps 175 | - unnecessary_const 176 | # - unnecessary_final # conflicts with prefer_final_locals 177 | - unnecessary_getters_setters 178 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 179 | - unnecessary_new 180 | - unnecessary_null_aware_assignments 181 | - unnecessary_null_in_if_null_operators 182 | - unnecessary_overrides 183 | - unnecessary_parenthesis 184 | - unnecessary_statements 185 | #- unnecessary_string_interpolations 186 | - unnecessary_this 187 | - unrelated_type_equality_checks 188 | # - unsafe_html # not yet tested 189 | - use_full_hex_values_for_flutter_colors 190 | # - use_function_type_syntax_for_parameters # not yet tested 191 | # - use_key_in_widget_constructors # not yet tested 192 | - use_rethrow_when_possible 193 | # - use_setters_to_change_properties # not yet tested 194 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 195 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 196 | - valid_regexps 197 | - void_checks 198 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/bin/debug.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 3 | import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; 4 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 5 | import 'package:analyzer_plugin/protocol/protocol_generated.dart'; 6 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 7 | import 'plugin.dart'; 8 | 9 | Future main(List args) async { 10 | final String root = Directory.current.parent.parent.parent.path; 11 | final AnalysisContextCollection collection = 12 | AnalysisContextCollection(includedPaths: [root]); 13 | 14 | final CandiesAnalyzerPlugin myPlugin = plugin; 15 | for (final AnalysisContext context in collection.contexts) { 16 | final CandiesAnalyzerPluginConfig config = myPlugin.configs.putIfAbsent( 17 | context.root, 18 | () => CandiesAnalyzerPluginConfig( 19 | context: context, 20 | pluginName: myPlugin.name, 21 | dartLints: myPlugin.dartLints, 22 | astVisitor: myPlugin.astVisitor, 23 | yamlLints: myPlugin.yamlLints, 24 | genericLints: myPlugin.genericLints, 25 | )); 26 | 27 | if (!config.shouldAnalyze) { 28 | continue; 29 | } 30 | for (final String file in context.contextRoot.analyzedFiles()) { 31 | //var errors = await context.currentSession.getErrors(file); 32 | if (!config.include(file)) { 33 | continue; 34 | } 35 | if (!myPlugin.shouldAnalyzeFile(file, context)) { 36 | continue; 37 | } 38 | 39 | final bool isAnalyzed = context.contextRoot.isAnalyzed(file); 40 | if (!isAnalyzed) { 41 | continue; 42 | } 43 | 44 | final List errors = 45 | (await myPlugin.getAnalysisErrorsForDebug( 46 | file, 47 | context, 48 | )) 49 | .toList(); 50 | for (final AnalysisError error in errors) { 51 | final List fixes = await myPlugin 52 | .getAnalysisErrorFixesForDebug( 53 | EditGetFixesParams(file, error.location.offset), context) 54 | .toList(); 55 | print(fixes.length); 56 | } 57 | 58 | print(errors.length); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/bin/plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:isolate'; 3 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 4 | import 'package:analyzer/dart/analysis/results.dart'; 5 | import 'package:analyzer/dart/ast/ast.dart'; 6 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 7 | import 'package:analyzer/dart/ast/token.dart'; 8 | import 'package:analyzer/dart/ast/visitor.dart'; 9 | 10 | import 'package:analyzer/source/source_range.dart'; 11 | import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; 12 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; 13 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 14 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_yaml.dart'; 15 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 16 | import 'package:analyzer/src/pubspec/pubspec_validator.dart'; 17 | 18 | CandiesAnalyzerPlugin get plugin => CustomLintPlugin(); 19 | 20 | // This file must be 'plugin.dart' 21 | void main(List args, SendPort sendPort) { 22 | // for performance, default is false, if you want to check log, set it to true. 23 | CandiesAnalyzerPluginLogger().shouldLog = true; 24 | CandiesAnalyzerPluginStarter.start( 25 | args, 26 | sendPort, 27 | plugin: plugin, 28 | ); 29 | } 30 | 31 | class CustomLintPlugin extends CandiesAnalyzerPlugin { 32 | @override 33 | String get name => 'custom_lint'; 34 | 35 | @override 36 | List get fileGlobsToAnalyze => const [ 37 | '**/*.dart', 38 | '**/*.yaml', 39 | '**/*.json', 40 | ]; 41 | 42 | @override 43 | List get dartLints => [ 44 | // add your dart lint here 45 | PerferCandiesClassPrefix(), 46 | ...super.dartLints, 47 | ]; 48 | 49 | @override 50 | List get yamlLints => [RemoveDependency(package: 'path')]; 51 | 52 | @override 53 | List get genericLints => [RemoveDuplicateValue()]; 54 | 55 | @override 56 | bool get showAnalysisErrorWithGitAuthor => false; 57 | 58 | @override 59 | bool get cacheErrorsIntoFile => true; 60 | } 61 | 62 | class PerferCandiesClassPrefix extends DartLint { 63 | @override 64 | String get code => 'perfer_candies_class_prefix'; 65 | 66 | @override 67 | String? get url => 68 | 'https://github.com/fluttercandies/candies_analyzer_plugin'; 69 | 70 | @override 71 | SyntacticEntity? matchLint(AstNode node) { 72 | if (node is ClassDeclaration) { 73 | final String name = node.name2.toString(); 74 | final int startIndex = _getClassNameStartIndex(name); 75 | if (!name.substring(startIndex).startsWith('Candies')) { 76 | return node.name2; 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | @override 83 | String get message => 'Define a class name start with Candies'; 84 | 85 | @override 86 | Future> getDartFixes( 87 | DartAnalysisError error, 88 | CandiesAnalyzerPluginConfig config, 89 | ) async { 90 | final ResolvedUnitResult resolvedUnitResult = error.result; 91 | 92 | final Iterable cacheErrors = config 93 | .getCacheErrors(resolvedUnitResult.path, code: code) 94 | .whereType(); 95 | 96 | final Map> references = 97 | _findClassReferences(cacheErrors, resolvedUnitResult); 98 | 99 | return [ 100 | await getDartFix( 101 | resolvedUnitResult: resolvedUnitResult, 102 | message: 'Use Candies as a class prefix.', 103 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 104 | _fix( 105 | error, 106 | resolvedUnitResult, 107 | dartFileEditBuilder, 108 | references[error]!, 109 | ); 110 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 111 | }, 112 | ), 113 | if (cacheErrors.length > 1) 114 | await getDartFix( 115 | resolvedUnitResult: resolvedUnitResult, 116 | message: 'Use Candies as a class prefix where possible in file.', 117 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 118 | for (final DartAnalysisError error in cacheErrors) { 119 | _fix( 120 | error, 121 | resolvedUnitResult, 122 | dartFileEditBuilder, 123 | references[error]!, 124 | ); 125 | } 126 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 127 | }, 128 | ), 129 | ]; 130 | } 131 | 132 | void _fix( 133 | DartAnalysisError error, 134 | ResolvedUnitResult resolvedUnitResult, 135 | DartFileEditBuilder dartFileEditBuilder, 136 | Set references, 137 | ) { 138 | final AstNode astNode = error.astNode; 139 | // get name node 140 | final Token nameNode = (astNode as ClassDeclaration).name2; 141 | final String nameString = nameNode.lexeme; 142 | 143 | final int startIndex = _getClassNameStartIndex(nameString); 144 | final String replace = 145 | '${nameString.substring(0, startIndex)}Candies${nameString.substring(startIndex)}'; 146 | 147 | for (final SyntacticEntity match in references) { 148 | dartFileEditBuilder.addSimpleReplacement( 149 | SourceRange(match.offset, match.length), replace); 150 | } 151 | } 152 | 153 | Map> _findClassReferences( 154 | Iterable errors, 155 | ResolvedUnitResult resolvedUnitResult, 156 | ) { 157 | final Map> references = 158 | >{}; 159 | final Map classNames = 160 | {}; 161 | 162 | for (final DartAnalysisError error in errors) { 163 | classNames[(error.astNode as ClassDeclaration).name2.lexeme] = error; 164 | references[error] = {}; 165 | } 166 | 167 | resolvedUnitResult.unit 168 | .accept(_FindClassReferenceVisitor(references, classNames)); 169 | 170 | return references; 171 | } 172 | 173 | int _getClassNameStartIndex(String nameString) { 174 | int index = 0; 175 | while (nameString[index] == '_') { 176 | index++; 177 | if (index == nameString.length - 1) { 178 | break; 179 | } 180 | } 181 | return index; 182 | } 183 | } 184 | 185 | class _FindClassReferenceVisitor extends GeneralizingAstVisitor { 186 | _FindClassReferenceVisitor(this.references, this.classNames); 187 | final Map> references; 188 | final Map classNames; 189 | 190 | @override 191 | void visitNode(AstNode node) { 192 | if (node.childEntities.length == 1) { 193 | final String source = node.toSource(); 194 | if (classNames.keys.contains(source)) { 195 | references[classNames[source]]!.add(node); 196 | return; 197 | } 198 | } 199 | super.visitNode(node); 200 | } 201 | } 202 | 203 | class RemoveDependency extends YamlLint { 204 | RemoveDependency({required this.package}); 205 | final String package; 206 | @override 207 | String get code => 'remove_${package}_dependency'; 208 | 209 | @override 210 | String get message => 'Remove $package dependency'; 211 | 212 | @override 213 | String? get correction => 'Remove $package dependency'; 214 | 215 | @override 216 | AnalysisErrorSeverity get severity => AnalysisErrorSeverity.WARNING; 217 | 218 | @override 219 | Iterable matchLint( 220 | YamlNode root, 221 | String content, 222 | LineInfo lineInfo, 223 | ) sync* { 224 | if (root is YamlMap && root.containsKey(PubspecField.DEPENDENCIES_FIELD)) { 225 | final YamlNode dependencies = 226 | root.nodes[PubspecField.DEPENDENCIES_FIELD]!; 227 | if (dependencies is YamlMap && dependencies.containsKey(package)) { 228 | final YamlNode get = dependencies.nodes[package]!; 229 | int start = dependencies.span.start.offset; 230 | final int end = get.span.start.offset; 231 | final int index = content.substring(start, end).indexOf('$package: '); 232 | start += index; 233 | yield SourceRange(start, get.span.end.offset - start); 234 | } 235 | } 236 | } 237 | 238 | /// It doesn't work for now. 239 | /// https://github.com/dart-lang/sdk/issues/50306 240 | /// leave it in case dart team maybe support it someday in the future 241 | @override 242 | Future> getYamlFixes( 243 | AnalysisContext analysisContext, 244 | String path, 245 | YamlAnalysisError error, 246 | CandiesAnalyzerPluginConfig config, 247 | ) async => 248 | [ 249 | await getYamlFix( 250 | analysisContext: analysisContext, 251 | path: path, 252 | message: 'Remove $package Dependency', 253 | buildYamlFileEdit: (YamlFileEditBuilder builder) { 254 | builder.addSimpleReplacement( 255 | SourceRange(error.location.offset, error.location.length), 256 | ''); 257 | }) 258 | ]; 259 | } 260 | 261 | class RemoveDuplicateValue extends GenericLint { 262 | @override 263 | String get code => 'remove_duplicate_value'; 264 | 265 | @override 266 | Iterable matchLint( 267 | String content, 268 | String file, 269 | LineInfo lineInfo, 270 | ) sync* { 271 | if (isFileType(file: file, type: '.json')) { 272 | final Map map = 273 | jsonDecode(content) as Map; 274 | 275 | final Map duplicate = {}; 276 | final Map checkDuplicate = {}; 277 | for (final dynamic key in map.keys) { 278 | final dynamic value = map[key]; 279 | if (checkDuplicate.containsKey(value)) { 280 | duplicate[key] = value; 281 | duplicate[checkDuplicate[value]] = value; 282 | } 283 | checkDuplicate[value] = key; 284 | } 285 | 286 | if (duplicate.isNotEmpty) { 287 | for (final dynamic key in duplicate.keys) { 288 | final int start = content.indexOf('"$key"'); 289 | final dynamic value = duplicate[key]; 290 | final int end = content.indexOf( 291 | '"$value"', 292 | start, 293 | ) + 294 | value.toString().length + 295 | 1; 296 | 297 | final int lineNumber = lineInfo.getLocation(end).lineNumber; 298 | 299 | bool hasComma = false; 300 | int commaIndex = end; 301 | int commaLineNumber = lineInfo.getLocation(commaIndex).lineNumber; 302 | 303 | while (!hasComma && commaLineNumber == lineNumber) { 304 | commaIndex++; 305 | final String char = content[commaIndex]; 306 | hasComma = char == ','; 307 | commaLineNumber = lineInfo.getLocation(commaIndex).lineNumber; 308 | } 309 | 310 | yield SourceRange(start, (hasComma ? commaIndex : end) + 1 - start); 311 | } 312 | } 313 | } 314 | } 315 | 316 | @override 317 | String get message => 'remove duplicate value'; 318 | 319 | /// It doesn't work for now. 320 | /// https://github.com/dart-lang/sdk/issues/50306 321 | /// leave it in case dart team maybe support it someday in the future 322 | @override 323 | Future> getGenericFixes( 324 | AnalysisContext analysisContext, 325 | String path, 326 | GenericAnalysisError error, 327 | CandiesAnalyzerPluginConfig config, 328 | ) async => 329 | [ 330 | await getGenericFix( 331 | analysisContext: analysisContext, 332 | path: path, 333 | message: 'Remove duplicate value', 334 | buildFileEdit: (FileEditBuilder builder) { 335 | builder.addSimpleReplacement( 336 | SourceRange(error.location.offset, error.location.length), 337 | ''); 338 | }) 339 | ]; 340 | } 341 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: dead_code 2 | 3 | import 'dart:io'; 4 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 5 | import 'package:path/path.dart'; 6 | 7 | import 'plugin.dart'; 8 | 9 | Future main(List args) async { 10 | final String workingDirectory = 11 | args.isNotEmpty ? args.first : Directory.current.path; 12 | final Stopwatch stopwatch = Stopwatch(); 13 | stopwatch.start(); 14 | // if false, analyze whole workingDirectory 15 | const bool onlyAnalyzeChangedFiles = true; 16 | final String gitRoot = CandiesAnalyzerPlugin.processRun( 17 | executable: 'git', 18 | arguments: 'rev-parse --show-toplevel', 19 | workingDirectory: workingDirectory, 20 | ).trim(); 21 | // find diff files. 22 | final List diff = CandiesAnalyzerPlugin.processRun( 23 | executable: 'git', 24 | arguments: 'diff --name-status', 25 | throwException: false, 26 | workingDirectory: workingDirectory, 27 | ).trim().split('\n').where((String e) { 28 | //M CHANGELOG.md 29 | //D CHANGELOG.md 30 | // ignore delete file 31 | 32 | return e.toUpperCase().startsWith('M'); 33 | }).map((String e) { 34 | return join(gitRoot, e.replaceFirst('M', '').trim()); 35 | }).toList(); 36 | 37 | // git ls-files --others --exclude-standard 38 | final List untracked = CandiesAnalyzerPlugin.processRun( 39 | executable: 'git', 40 | arguments: 'ls-files --others --exclude-standard', 41 | throwException: false, 42 | workingDirectory: workingDirectory, 43 | ) 44 | .trim() 45 | .split('\n') 46 | .map((String e) => join(workingDirectory, e).trim()) 47 | .toList(); 48 | 49 | final List analyzeFiles = [...diff, ...untracked] 50 | .where((String element) => element.startsWith(workingDirectory)) 51 | .toList(); 52 | 53 | if (onlyAnalyzeChangedFiles && analyzeFiles.isEmpty) { 54 | stopwatch.stop(); 55 | return; 56 | } 57 | 58 | // get error from CandiesAnalyzerPlugin 59 | final List errors = await CandiesAnalyzerPlugin.getCandiesErrorInfos( 60 | workingDirectory, 61 | plugin, 62 | analyzeFiles: onlyAnalyzeChangedFiles ? analyzeFiles : null, 63 | ); 64 | 65 | // get errors from dart analyze command 66 | errors.addAll(CandiesAnalyzerPlugin.getErrorInfosFromDartAnalyze( 67 | workingDirectory, 68 | analyzeFiles: onlyAnalyzeChangedFiles ? analyzeFiles : null, 69 | )); 70 | stopwatch.stop(); 71 | 72 | _printErrors(errors, stopwatch.elapsed.inMilliseconds); 73 | } 74 | 75 | void _printErrors(List errors, int inMilliseconds) { 76 | final String seconds = (inMilliseconds / 1000).toStringAsFixed(2); 77 | if (errors.isEmpty) { 78 | print('No issues found! ${seconds}s'); 79 | } else { 80 | print(''); 81 | print(errors 82 | .map((String e) => ' ${e.getHighlightErrorInfo()}') 83 | .join('\n\n')); 84 | print('\n${errors.length} issues found.' 85 | .wrapAnsiCode(foregroundColor: AnsiCodeForegroundColor.red) + 86 | ' ${seconds}s'); 87 | print('Please fix the errors and then submit the code.' 88 | .wrapAnsiCode(foregroundColor: AnsiCodeForegroundColor.red)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/lib/custom_lint_analyzer_plugin.dart: -------------------------------------------------------------------------------- 1 | int calculate() { 2 | return 6 * 7; 3 | } 4 | -------------------------------------------------------------------------------- /example/custom_lint/tools/analyzer_plugin/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: custom_lint_analyzer_plugin 2 | description: A sample command-line application. 3 | version: 1.0.0 4 | # homepage: https://www.example.com 5 | 6 | environment: 7 | sdk: '>=2.17.6 <3.0.0' 8 | 9 | dependencies: 10 | candies_analyzer_plugin: any 11 | path: any 12 | analyzer: any 13 | analyzer_plugin: any 14 | dependency_overrides: 15 | 16 | dev_dependencies: 17 | lints: any 18 | test: any 19 | -------------------------------------------------------------------------------- /example/flutter_package/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /example/flutter_package/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f1875d570e39de09040c8f79aa13cc56baab8db1 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/flutter_package/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /example/flutter_package/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /example/flutter_package/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /example/flutter_package/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | analyzer: 6 | # zmtzawqlp 7 | plugins: 8 | custom_lint 9 | -------------------------------------------------------------------------------- /example/flutter_package/lib/extension/int.dart: -------------------------------------------------------------------------------- 1 | extension IntE4 on int { 2 | /// f method 3 | int f(int a) => this; 4 | 5 | /// getF property 6 | int get getF => this; 7 | 8 | int plus(int input) => input + this; 9 | } 10 | -------------------------------------------------------------------------------- /example/flutter_package/lib/flutter_package.dart: -------------------------------------------------------------------------------- 1 | library flutter_package; 2 | 3 | export 'extension/int.dart'; 4 | export 'test.dart'; 5 | -------------------------------------------------------------------------------- /example/flutter_package/lib/test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_package/test1.dart'; 2 | 3 | class TestApi { 4 | static final Common _common = Common(); 5 | static Common get common => _common; 6 | } 7 | -------------------------------------------------------------------------------- /example/flutter_package/lib/test1.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class Common { 4 | void aaa() { 5 | if (kDebugMode) { 6 | print('aaa'); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/flutter_package/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_package 2 | description: A new Flutter package project. 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: ">=2.17.6 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^2.0.0 18 | # zmtzawqlp 19 | custom_lint: 20 | path: ../custom_lint/ 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter packages. 25 | flutter: 26 | 27 | # To add assets to your package, add an assets section, like this: 28 | # assets: 29 | # - images/a_dot_burr.jpeg 30 | # - images/a_dot_ham.jpeg 31 | # 32 | # For details regarding assets in packages, see 33 | # https://flutter.dev/assets-and-images/#from-packages 34 | # 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.dev/assets-and-images/#resolution-aware 37 | 38 | # To add custom fonts to your package, add a fonts section here, 39 | # in this "flutter" section. Each entry in this list should have a 40 | # "family" key with the font family name, and a "fonts" key with a 41 | # list giving the asset and other descriptors for the font. For 42 | # example: 43 | # fonts: 44 | # - family: Schyler 45 | # fonts: 46 | # - asset: fonts/Schyler-Regular.ttf 47 | # - asset: fonts/Schyler-Italic.ttf 48 | # style: italic 49 | # - family: Trajan Pro 50 | # fonts: 51 | # - asset: fonts/TrajanPro.ttf 52 | # - asset: fonts/TrajanPro_Bold.ttf 53 | # weight: 700 54 | # 55 | # For details regarding fonts in packages, see 56 | # https://flutter.dev/custom-fonts/#from-packages 57 | -------------------------------------------------------------------------------- /example/lib/class_prefix.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: perfer_doc_comments 2 | import 'package:flutter/material.dart'; 3 | 4 | class MyWidget extends StatefulWidget { 5 | const MyWidget({Key? key}) : super(key: key); 6 | 7 | @override 8 | State createState() => _MyWidgetState(); 9 | } 10 | 11 | class _MyWidgetState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/lib/comments.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_file 2 | import 'package:flutter/material.dart'; 3 | 4 | /// This is TestA 5 | class TestA { 6 | /// This is A i 7 | int i = 1; 8 | void greet(String name) { 9 | /* Assume we have a valid name. */ 10 | // ignore: avoid_print 11 | print('Hi, $name!'); 12 | } 13 | } 14 | 15 | /* This is TestB */ 16 | class TestB { 17 | /* This is B i */ 18 | int i = 1; 19 | } 20 | 21 | // This is TestC 22 | class TestC { 23 | // This is C i 24 | int i = 1; 25 | } 26 | 27 | /// This is TestD 28 | /// 29 | /// 30 | /// 31 | /// */ 32 | class TestD { 33 | /* This is D i */ 34 | int i = 1; 35 | } 36 | 37 | int i = 1; 38 | 39 | class MyWidget extends StatefulWidget { 40 | const MyWidget({Key? key, required this.s}) : super(key: key); 41 | final int s; 42 | @override 43 | State1 createState() => _MyWidgetState(); 44 | } 45 | 46 | class _MyWidgetState extends State1 { 47 | int i = 0; 48 | int _j = 0; 49 | int get j => _j; 50 | set j(int value) { 51 | if (_j != value) { 52 | _j = value; 53 | } 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return Container(); 59 | } 60 | } 61 | 62 | abstract class State1 extends State {} 63 | 64 | enum Enum { 65 | test, 66 | test1, 67 | } 68 | 69 | class Test with MixinTest { 70 | @override 71 | String get a => '1'; 72 | 73 | @override 74 | void test() {} 75 | } 76 | 77 | mixin MixinTest { 78 | String get a; 79 | 80 | void test(); 81 | } 82 | 83 | void test() {} 84 | 85 | typedef TestFucntion = Function(); 86 | -------------------------------------------------------------------------------- /example/lib/dispose.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: perfer_candies_class_prefix, perfer_doc_comments, avoid_print 2 | import 'package:flutter/material.dart'; 3 | 4 | class MyWidget extends StatefulWidget { 5 | const MyWidget({Key? key}) : super(key: key); 6 | 7 | @override 8 | State createState() => _MyWidgetState(); 9 | } 10 | 11 | class _MyWidgetState extends State { 12 | @override 13 | void dispose() { 14 | super.dispose(); 15 | print('ddd'); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container(); 21 | } 22 | } 23 | 24 | class MyWidget1 extends StatefulWidget { 25 | const MyWidget1({Key? key}) : super(key: key); 26 | 27 | @override 28 | State createState() => _MyWidget1State(); 29 | } 30 | 31 | class _MyWidget1State extends State { 32 | @override 33 | void dispose() { 34 | super.dispose(); 35 | print('ddd'); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return Container(); 41 | } 42 | } 43 | 44 | class MyWidget2 extends StatefulWidget { 45 | const MyWidget2({Key? key}) : super(key: key); 46 | 47 | @override 48 | State createState() => _MyWidget2State(); 49 | } 50 | 51 | class _MyWidget2State extends State { 52 | @override 53 | void dispose() { 54 | print('ddd'); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Container(); 60 | } 61 | } 62 | 63 | class MyWidget3 extends StatefulWidget { 64 | const MyWidget3({Key? key}) : super(key: key); 65 | 66 | @override 67 | State createState() => _MyWidget3State(); 68 | } 69 | 70 | class _MyWidget3State extends State { 71 | @override 72 | void dispose() { 73 | print('ddd'); 74 | } 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return Container(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/lib/exclude/test.dart: -------------------------------------------------------------------------------- 1 | class MyWidget extends StatefulWidget { 2 | const MyWidget({Key? key}) : super(key: key); 3 | 4 | @override 5 | State createState() => _MyWidgetState(); 6 | } 7 | 8 | class _MyWidgetState extends State { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/lib/extension/int.dart: -------------------------------------------------------------------------------- 1 | extension IntE on int { 2 | /// test method 3 | int a(int a) => this; 4 | 5 | void aa() => this; 6 | 7 | /// getA property 8 | int get getA => this; 9 | } 10 | 11 | extension IntE1 on int { 12 | /// b method 13 | int b(int a) => this; 14 | 15 | /// getB property 16 | int get getB => this; 17 | 18 | /// getBB property 19 | int get getBB => this; 20 | } 21 | 22 | extension IntE5 on int? { 23 | /// b method 24 | int? c(int a) => this; 25 | 26 | /// getC property 27 | int? get getC => this; 28 | 29 | /// getCC method 30 | int? getCC(int? a) => this; 31 | int? getCCC({int? a}) => this; 32 | } 33 | -------------------------------------------------------------------------------- /example/lib/extension/int_copy.dart: -------------------------------------------------------------------------------- 1 | extension IntE2 on int { 2 | /// D method 3 | int d(int a) => this; 4 | 5 | /// getD property 6 | int get getD => this; 7 | } 8 | 9 | extension IntE3 on int { 10 | /// e method 11 | int e(int a) => this; 12 | 13 | /// getE property 14 | int get getE => this; 15 | } 16 | -------------------------------------------------------------------------------- /example/lib/include/test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MyWidget extends StatefulWidget { 4 | const MyWidget({Key? key}) : super(key: key); 5 | 6 | @override 7 | State createState() => _MyWidgetState(); 8 | } 9 | 10 | class _MyWidgetState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_local_variable 2 | 3 | // import 'package:dartx/dartx.dart'; 4 | // import 'package:example/extension/int.dart'; 5 | 6 | import 'package:example/singleton.dart'; 7 | import 'package:flutter/material.dart'; 8 | // import 'comments.dart' as comments; 9 | // import 'dart:async'; 10 | 11 | // import 'package:time/time.dart'; 12 | // import 'package:dartx/dartx.dart'; 13 | 14 | import 'package:flutter_package/flutter_package.dart'; 15 | import 'package:flutter_package/flutter_package.dart' as flutter_package; 16 | //import 'package:flutter_package/extension/int.dart'; 17 | import 'test.dart' as test; 18 | 19 | void main() { 20 | int i = 1; 21 | i.f(1); 22 | flutter_package.TestApi.common.aaa(); 23 | Singleton().printInfo(); 24 | var num = Singleton().num; 25 | const Singleton1().printInfo(); 26 | var num1 = const Singleton1().num; 27 | test.Test test1 = test.Test(); 28 | List list = []; 29 | 30 | runApp(const MyApp()); 31 | } 32 | 33 | class MyApp extends StatelessWidget { 34 | const MyApp({Key? key}) : super(key: key); 35 | 36 | // This widget is the root of your application. 37 | @override 38 | Widget build(BuildContext context) { 39 | // return const comments.MyWidget( 40 | // s: 1, 41 | // ); 42 | /// test 43 | return MaterialApp( 44 | title: 'Flutter Demo', 45 | theme: ThemeData( 46 | // This is the theme of your application. 47 | // 48 | // Try running your application with "flutter run". You'll see the 49 | // application has a blue toolbar. Then, without quitting the app, try 50 | // changing the primarySwatch below to Colors.green and then invoke 51 | // "hot reload" (press "r" in the console where you ran "flutter run", 52 | // or simply save your changes to "hot reload" in a Flutter IDE). 53 | // Notice that the counter didn't reset back to zero; the application 54 | // is not restarted. 55 | primarySwatch: Colors.blue, 56 | ), 57 | home: const MyHomePage(title: 'Flutter Demo Home Page'), 58 | ); 59 | } 60 | } 61 | 62 | class MyHomePage extends StatefulWidget { 63 | const MyHomePage({Key? key, required this.title}) : super(key: key); 64 | 65 | // This widget is the home page of your application. It is stateful, meaning 66 | // that it has a State object (defined below) that contains fields that affect 67 | // how it looks. 68 | 69 | // This class is the configuration for the state. It holds the values (in this 70 | // case the title) provided by the parent (in this case the App widget) and 71 | // used by the build method of the State. Fields in a Widget subclass are 72 | // always marked "final". 73 | 74 | final String title; 75 | 76 | @override 77 | State createState() => _MyHomePageState(); 78 | } 79 | 80 | class _MyHomePageState extends State { 81 | int _counter = 0; 82 | 83 | void _incrementCounter() { 84 | setState(() { 85 | // This call to setState tells the Flutter framework that something has 86 | // changed in this State, which causes it to rerun the build method below 87 | // so that the display can reflect the updated values. If we changed 88 | // _counter without calling setState(), then the build method would not be 89 | // called again, and so nothing would appear to happen. 90 | _counter++; 91 | }); 92 | } 93 | 94 | void test() { 95 | setState(() { 96 | // This call to setState tells the Flutter framework that something has 97 | // changed in this State, which causes it to rerun the build method below 98 | // so that the display can reflect the updated values. If we changed 99 | // _counter without calling setState(), then the build method would not be 100 | // called again, and so nothing would appear to happen. 101 | _counter++; 102 | }); 103 | } 104 | 105 | @override 106 | void dispose() { 107 | //super.dispose(); 108 | super.dispose(); 109 | int i = 1; 110 | } 111 | 112 | //@override 113 | //void dispose() => _counter++; 114 | //@override 115 | //void dispose() => super.dispose(); 116 | //@override 117 | //void dispose() => {}; 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | // This method is rerun every time setState is called, for instance as done 122 | // by the _incrementCounter method above. 123 | // 124 | // The Flutter framework has been optimized to make rerunning build methods 125 | // fast, so that you can just rebuild anything that needs updating rather 126 | // than having to individually change instances of widgets. 127 | return Scaffold( 128 | appBar: AppBar( 129 | // Here we take the value from the MyHomePage object that was created by 130 | // the App.build method, and use it to set our appbar title. 131 | title: Text(widget.title), 132 | ), 133 | body: Center( 134 | // Center is a layout widget. It takes a single child and positions it 135 | // in the middle of the parent. 136 | child: Column( 137 | // Column is also a layout widget. It takes a list of children and 138 | // arranges them vertically. By default, it sizes itself to fit its 139 | // children horizontally, and tries to be as tall as its parent. 140 | // 141 | // Invoke "debug painting" (press "p" in the console, choose the 142 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 143 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 144 | // to see the wireframe for each widget. 145 | // 146 | // Column has various properties to control how it sizes itself and 147 | // how it positions its children. Here we use mainAxisAlignment to 148 | // center the children vertically; the main axis here is the vertical 149 | // axis because Columns are vertical (the cross axis would be 150 | // horizontal). 151 | mainAxisAlignment: MainAxisAlignment.center, 152 | children: [ 153 | const Text( 154 | 'You have pushed the button this many times:', 155 | ), 156 | Text( 157 | '$_counter', 158 | style: Theme.of(context).textTheme.headline4, 159 | ), 160 | Image.asset('name'), 161 | GestureDetector( 162 | onTap: () => setState(() {}), 163 | ), 164 | GestureDetector( 165 | onTap: () { 166 | Navigator.push( 167 | context, MaterialPageRoute(builder: (b) => Container())); 168 | }, 169 | ), 170 | ], 171 | ), 172 | ), 173 | floatingActionButton: FloatingActionButton( 174 | onPressed: _incrementCounter, 175 | tooltip: 'Increment', 176 | child: const Icon(Icons 177 | .add)), // This trailing comma makes auto-formatting nicer for build methods. 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /example/lib/singleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class Singleton { 4 | factory Singleton() => _singleton; 5 | Singleton._(); 6 | static final Singleton _singleton = Singleton._(); 7 | void printInfo() { 8 | if (kDebugMode) { 9 | print('object'); 10 | } 11 | } 12 | 13 | int get num => 1; 14 | } 15 | 16 | class Singleton1 { 17 | const Singleton1(); 18 | void printInfo() { 19 | if (kDebugMode) { 20 | print('object'); 21 | } 22 | } 23 | 24 | int get num => 1; 25 | } 26 | 27 | // class Singleton1 { 28 | // Singleton1._(); 29 | // static final Singleton1 _singleton1 = Singleton1._(); 30 | // static Singleton1 get instance => _singleton1; 31 | // void printInfo() { 32 | // if (kDebugMode) { 33 | // print('object'); 34 | // } 35 | // } 36 | // } 37 | -------------------------------------------------------------------------------- /example/lib/test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | part 'test_part.dart'; 4 | 5 | ///abc 6 | //abc 7 | class Test {} 8 | -------------------------------------------------------------------------------- /example/lib/test_part.dart: -------------------------------------------------------------------------------- 1 | part of 'test.dart'; 2 | 3 | extension TestE on Test { 4 | void printInfo(String info) { 5 | if (kDebugMode) { 6 | print(info); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/trailing_comma.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_field,perfer_doc_comments,perfer_candies_class_prefix 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class MyWidget extends StatefulWidget { 6 | const MyWidget( 7 | {Key? key, 8 | required this.value, 9 | required this.value1, 10 | required this.value2}) 11 | : super(key: key); 12 | 13 | final String value; 14 | final String value1; 15 | final String value2; 16 | @override 17 | State createState() => _MyWidgetState(); 18 | } 19 | 20 | class _MyWidgetState extends State { 21 | final List _list = [ 22 | '1', 23 | '2', 24 | '3', 25 | '1000000000000000000', 26 | 'dadadjakl' 27 | ]; 28 | 29 | final Set _set = { 30 | '1', 31 | '2', 32 | '3', 33 | '1000000000000000000', 34 | 'dadadjakl' 35 | }; 36 | 37 | final Map _map = { 38 | '1': '1', 39 | '2': '2', 40 | '3': '3', 41 | '1000000000000000000': '1000000000000000000', 42 | 'dadadjakl': 'dadadjakl' 43 | }; 44 | @override 45 | void initState() { 46 | super.initState(); 47 | // ignore: unused_local_variable 48 | Test test = Test('dadafsfsff', 'dadafsfsdfdfsd', 'sdadfdfsfsfasd', 49 | 'dadfdfdfsfsda', 'dadadadasfd', 'dafefgfdadasdad'); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return Scaffold( 55 | body: Container( 56 | alignment: Alignment.center, 57 | decoration: BoxDecoration( 58 | color: Colors.red, border: Border.all(color: Colors.red))), 59 | ); 60 | } 61 | } 62 | 63 | class Test { 64 | Test(this.value, this.value1, this.value2, this.value3, this.value4, 65 | this.value5); 66 | final String value; 67 | final String value1; 68 | final String value2; 69 | final String value3; 70 | final String value4; 71 | final String value5; 72 | } 73 | -------------------------------------------------------------------------------- /example/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # project path 4 | base_dir="{0}" 5 | 6 | dart format "$base_dir" 7 | 8 | # pre_commit.dart path 9 | pre_commit="{1}" 10 | 11 | echo "Checking the code before submit..." 12 | echo "Analyzing $base_dir..." 13 | 14 | info=$(dart "$pre_commit" "$base_dir") 15 | 16 | echo "$info" 17 | 18 | if [[ -n $info && $info != *"No issues found"* ]];then 19 | exit 1 20 | fi 21 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.17.6 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | path: any 33 | flutter_package: 34 | path: flutter_package/ 35 | 36 | # The following adds the Cupertino Icons font to your application. 37 | # Use with the CupertinoIcons class for iOS style icons. 38 | cupertino_icons: ^1.0.2 39 | #dartx: any 40 | #collection: any 41 | dev_dependencies: 42 | flutter_test: 43 | sdk: flutter 44 | # zmtzawqlp 45 | custom_lint: 46 | path: custom_lint/ 47 | # The "flutter_lints" package below contains a set of recommended lints to 48 | # encourage good coding practices. The lint set provided by the package is 49 | # activated in the `analysis_options.yaml` file located at the root of your 50 | # package. See that file for information about deactivating specific lint 51 | # rules and activating additional ones. 52 | flutter_lints: ^2.0.0 53 | 54 | # For information on the generic Dart part of this file, see the 55 | # following page: https://dart.dev/tools/pub/pubspec 56 | 57 | # The following section is specific to Flutter packages. 58 | flutter: 59 | 60 | # The following line ensures that the Material Icons font is 61 | # included with your application, so that you can use the icons in 62 | # the material Icons class. 63 | uses-material-design: true 64 | 65 | # To add assets to your application, add an assets section, like this: 66 | # assets: 67 | # - images/a_dot_burr.jpeg 68 | # - images/a_dot_ham.jpeg 69 | 70 | # An image asset can refer to one or more resolution-specific "variants", see 71 | # https://flutter.dev/assets-and-images/#resolution-aware 72 | 73 | # For details regarding adding assets from package dependencies, see 74 | # https://flutter.dev/assets-and-images/#from-packages 75 | 76 | # To add custom fonts to your application, add a fonts section here, 77 | # in this "flutter" section. Each entry in this list should have a 78 | # "family" key with the font family name, and a "fonts" key with a 79 | # list giving the asset and other descriptors for the font. For 80 | # example: 81 | # fonts: 82 | # - family: Schyler 83 | # fonts: 84 | # - asset: fonts/Schyler-Regular.ttf 85 | # - asset: fonts/Schyler-Italic.ttf 86 | # style: italic 87 | # - family: Trajan Pro 88 | # fonts: 89 | # - asset: fonts/TrajanPro.ttf 90 | # - asset: fonts/TrajanPro_Bold.ttf 91 | # weight: 700 92 | # 93 | # For details regarding fonts from package dependencies, 94 | # see https://flutter.dev/custom-fonts/#from-packages 95 | -------------------------------------------------------------------------------- /lib/candies_analyzer_plugin.dart: -------------------------------------------------------------------------------- 1 | library candies_analyzer_plugin; 2 | 3 | export 'package:yaml/yaml.dart'; 4 | export 'package:pubspec_parse/pubspec_parse.dart'; 5 | 6 | export 'src/plugin_starter.dart'; 7 | export 'src/plugin.dart'; 8 | export 'src/error/lints/lint.dart'; 9 | export 'src/error/lints/yaml_lint.dart'; 10 | export 'src/error/lints/dart/call_super_dispose.dart'; 11 | export 'src/error/lints/dart/dart_lint.dart'; 12 | export 'src/error/lints/dart/perfer_class_prefix.dart'; 13 | export 'src/error/lints/dart/prefer_asset_const.dart'; 14 | export 'src/error/lints/dart/prefer_named_routes.dart'; 15 | export 'src/error/lints/dart/prefer_safe_set_state.dart'; 16 | export 'src/error/lints/dart/perfer_doc_comments.dart'; 17 | export 'src/error/lints/dart/unused_file.dart'; 18 | export 'src/error/lints/dart/perfer_singleton.dart'; 19 | export 'src/error/lints/dart/good_doc_comments.dart'; 20 | export 'src/error/lints/dart/prefer_trailing_comma.dart'; 21 | export 'src/error/lints/generic_lint.dart'; 22 | export 'src/error/lints/dart/util/analyzer.dart'; 23 | export 'src/error/lints/dart/util/ast.dart'; 24 | export 'src/error/lints/dart/util/utils.dart' 25 | hide isDartFileName, isPubspecFileName; 26 | export 'src/extension.dart'; 27 | export 'src/ignore_info.dart'; 28 | export 'src/log.dart'; 29 | export 'src/config.dart'; 30 | export 'src/ansi_code.dart'; 31 | export 'src/error/error/dart.dart'; 32 | export 'src/error/error/yaml.dart'; 33 | export 'src/error/error/generic.dart'; 34 | -------------------------------------------------------------------------------- /lib/src/ansi_code.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:io/ansi.dart' as ansi; 4 | 5 | /// Font color range: 30-37 6 | /// 30: Black 7 | /// 31: Red 8 | /// 32: Green 9 | /// 33: Yellow 10 | /// 34: Blue 11 | /// 35: Purple 12 | /// 36: Dark Green 13 | /// 37: Gray White 14 | enum AnsiCodeForegroundColor { 15 | black(ansi.black), 16 | 17 | red(ansi.red), 18 | 19 | green(ansi.green), 20 | 21 | yellow(ansi.yellow), 22 | 23 | blue(ansi.blue), 24 | 25 | magenta(ansi.magenta), 26 | 27 | cyan(ansi.cyan), 28 | 29 | lightGray(ansi.lightGray), 30 | 31 | defaultForeground(ansi.defaultForeground), 32 | 33 | darkGray(ansi.darkGray), 34 | 35 | lightRed(ansi.lightRed), 36 | 37 | lightGreen(ansi.lightGreen), 38 | 39 | lightYellow(ansi.lightYellow), 40 | 41 | lightBlue(ansi.lightBlue), 42 | 43 | lightMagenta(ansi.lightMagenta), 44 | 45 | lightCyan(ansi.lightCyan), 46 | 47 | white(ansi.white); 48 | 49 | const AnsiCodeForegroundColor(this.value); 50 | final ansi.AnsiCode value; 51 | } 52 | 53 | /// Background color range: 40-47 54 | /// 40: Black 55 | /// 41: Red 56 | /// 42: Green 57 | /// 43: Yellow 58 | /// 44: Blue 59 | /// 45: Purple 60 | /// 46: Dark Green 61 | /// 47: Gray White 62 | enum AnsiCodeBackgroundColor { 63 | black(ansi.backgroundBlack), 64 | 65 | red(ansi.backgroundRed), 66 | 67 | green(ansi.backgroundGreen), 68 | 69 | yellow(ansi.backgroundYellow), 70 | 71 | blue(ansi.backgroundBlue), 72 | 73 | purple(ansi.backgroundMagenta), 74 | 75 | darkGreen(ansi.backgroundCyan), 76 | 77 | grayWhite(ansi.backgroundLightGray), 78 | 79 | backgroundDefault(ansi.backgroundDefault), 80 | 81 | gray(ansi.backgroundDarkGray), 82 | 83 | lightRed(ansi.backgroundLightRed), 84 | 85 | lightGreen(ansi.backgroundLightGreen), 86 | 87 | lightYellow(ansi.backgroundLightYellow), 88 | 89 | lightBlue(ansi.backgroundLightBlue), 90 | 91 | lightMagenta(ansi.backgroundLightMagenta), 92 | 93 | lightCyan(ansi.backgroundLightCyan), 94 | 95 | white(ansi.backgroundWhite); 96 | 97 | const AnsiCodeBackgroundColor(this.value); 98 | final ansi.AnsiCode value; 99 | } 100 | 101 | /// Effect range: 0-8 102 | /// 0: No effect 103 | /// 1: Highlight (darken) display 104 | /// 2: Low light (weaken) display 105 | /// 3: italic 106 | /// 4: Underline 107 | /// 5: Flicker 108 | /// 7: Reverse (replace background color and font color) 109 | /// 8: Hide 110 | /// 9: crossedOut 111 | enum AnsiCodeStyle { 112 | /// no effect 113 | noEffect(ansi.resetAll), 114 | 115 | /// highlight (darken) display 116 | bold(ansi.styleBold), 117 | 118 | /// Lowlight (weaken) display 119 | dim(ansi.styleDim), 120 | 121 | /// italic 122 | 123 | italic(ansi.styleItalic), 124 | 125 | /// underline 126 | underlined(ansi.styleUnderlined), 127 | 128 | /// flicker 129 | blink(ansi.styleBlink), 130 | 131 | /// reverse (replace background color and font color) 132 | reverse(ansi.styleReverse), 133 | 134 | /// hide 135 | hidden(ansi.styleHidden), 136 | 137 | /// crossedOut 138 | crossedOut(ansi.styleCrossedOut); 139 | 140 | const AnsiCodeStyle(this.value); 141 | final ansi.AnsiCode value; 142 | } 143 | 144 | extension AnsiCodeE on String { 145 | String wrapAnsiCode({ 146 | AnsiCodeForegroundColor? foregroundColor, 147 | AnsiCodeBackgroundColor? backgroundColor, 148 | AnsiCodeStyle? style, 149 | }) { 150 | // color is not working in pre-commit shell at Windows. 151 | if (Platform.isWindows) { 152 | return this; 153 | } 154 | // echo '\033[43;34;4m abc \033[0m' 155 | // \033[0m should call end with it, so that no effect the text after it 156 | // m is end falg 157 | return ansi.wrapWith( 158 | this, 159 | [ 160 | if (foregroundColor != null) foregroundColor.value, 161 | if (backgroundColor != null) backgroundColor.value, 162 | if (style != null) style.value, 163 | ], 164 | // \\033[0m 165 | forScript: true, 166 | ) ?? 167 | this; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/src/ast_visitor.dart: -------------------------------------------------------------------------------- 1 | part of 'config.dart'; 2 | 3 | /// The default AstVisitor to analyze lints 4 | class CandiesLintsAstVisitor extends GeneralizingAstVisitor 5 | with AstVisitorBase { 6 | @override 7 | void visitNode(AstNode node) { 8 | analyze(node); 9 | super.visitNode(node); 10 | } 11 | } 12 | 13 | /// AstVisitor to check lint 14 | /// 15 | mixin AstVisitorBase on AstVisitor { 16 | List? _lints; 17 | List get lints => _lints ??= []; 18 | 19 | bool analyze(AstNode node) { 20 | bool handle = false; 21 | for (final DartLint lint in lints) { 22 | handle = lint.analyze(node) || handle; 23 | } 24 | return handle; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/completion/completion_mixin.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implementation_imports 2 | 3 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 4 | import 'package:analyzer/dart/analysis/results.dart'; 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:analyzer_plugin/plugin/plugin.dart'; 7 | import 'package:analyzer_plugin/protocol/protocol_generated.dart'; 8 | import 'package:analyzer_plugin/utilities/completion/completion_core.dart'; 9 | import 'package:analyzer_plugin/src/utilities/completion/completion_core.dart'; 10 | import 'package:analyzer_plugin/utilities/generator.dart'; 11 | import 'package:candies_analyzer_plugin/src/completion/contributors/extension_member_contributor.dart'; 12 | import 'package:candies_analyzer_plugin/src/log.dart'; 13 | import 'package:analyzer/src/util/file_paths.dart' as file_paths; 14 | import 'package:path/path.dart' as path_context; 15 | 16 | mixin CandiesCompletionPlugin on ServerPlugin { 17 | final Set _accessibleExtensionsCache = {}; 18 | // final Set _unSolvedAccessibleExtensions = 19 | // {}; 20 | //String? _sdkPath; 21 | // save result for [ExtensionMemberContributor] 22 | Future saveAccessibleExtensions({ 23 | required AnalysisContext analysisContext, 24 | required String path, 25 | //bool solved = true, 26 | }) async { 27 | if (file_paths.isDart(path_context.context, path) && 28 | completionContributors 29 | .whereType() 30 | .isEmpty) { 31 | return; 32 | } 33 | final ResolvedUnitResult result = await getResolvedUnitResult(path); 34 | final Set accessibleExtensions = 35 | _accessibleExtensionsCache; 36 | //solved ? _accessibleExtensions : _unSolvedAccessibleExtensions; 37 | for (final ExtensionElement accessibleExtension 38 | in result.libraryElement.accessibleExtensions) { 39 | // skip dart.core 40 | if ( 41 | //accessibleExtension.library.name == 'dart.core' && 42 | accessibleExtension.library.isDartCore) { 43 | continue; 44 | } 45 | 46 | if (!accessibleExtensions.contains(accessibleExtension)) { 47 | CandiesAnalyzerPluginLogger().log( 48 | 'saveResolvedUnitResult at ${accessibleExtension.source.fullName}', 49 | root: analysisContext.contextRoot.root.path, 50 | ); 51 | accessibleExtensions.add(accessibleExtension); 52 | } 53 | } 54 | } 55 | 56 | @override 57 | Future contentChanged(List paths) { 58 | // clear result when file is changed 59 | _accessibleExtensionsCache.removeWhere( 60 | (ExtensionElement element) => paths.contains(element.source.fullName)); 61 | return super.contentChanged(paths); 62 | } 63 | 64 | /// The completionContributors to finish CompletionRequest 65 | List get completionContributors => 66 | [ 67 | ExtensionMemberContributor(), 68 | ]; 69 | 70 | /// Return the completion request that should be passes to the contributors 71 | /// returned from [getCompletionContributors]. 72 | /// 73 | /// Throw a [RequestFailure] if the request could not be created. 74 | Future getCompletionRequest( 75 | CompletionGetSuggestionsParams parameters) async { 76 | final ResolvedUnitResult result = 77 | await getResolvedUnitResult(parameters.file); 78 | return DartCompletionRequestImpl( 79 | resourceProvider, parameters.offset, result); 80 | } 81 | 82 | @override 83 | Future handleCompletionGetSuggestions( 84 | CompletionGetSuggestionsParams parameters, 85 | ) async { 86 | //final String path = parameters.file; 87 | final DartCompletionRequestImpl request = 88 | await getCompletionRequest(parameters); 89 | 90 | final CompletionGenerator generator = 91 | CompletionGenerator(completionContributors 92 | ..forEach( 93 | (CompletionContributor element) { 94 | if (element is ExtensionMemberContributor) { 95 | element.accessibleExtensions = _accessibleExtensionsCache; 96 | } 97 | }, 98 | )); 99 | final GeneratorResult result = 100 | await generator.generateCompletionResponse(request); 101 | result.sendNotifications(channel); 102 | CandiesAnalyzerPluginLogger().log( 103 | 'handleCompletionGetSuggestions: find Suggestions ${result.result?.results.length}个,${result.result?.results}', 104 | root: request.result.session.analysisContext.contextRoot.root.path, 105 | ); 106 | return result.result!; 107 | } 108 | 109 | // Future saveOtherPackagesAccessibleExtensions({ 110 | // required AnalysisContextCollection contextCollection, 111 | // }) async { 112 | // if (completionContributors 113 | // .whereType() 114 | // .isEmpty) { 115 | // return; 116 | // } 117 | // final List includedPaths = 118 | // contextCollection.contexts.map((AnalysisContext e) => e.root).toList(); 119 | // final Set ohterPaths = {}; 120 | // for (final AnalysisContext context in contextCollection.contexts) { 121 | // try { 122 | // final PackageGraph packageGraph = 123 | // await PackageGraph.forPath(context.root); 124 | // final Map dependencies = 125 | // _parseDependencyTypes(context.root); 126 | // for (final PackageNode package in packageGraph.allPackages.values) { 127 | // if (package.dependencyType == DependencyType.path) { 128 | // continue; 129 | // } 130 | // if (!dependencies.keys.contains(package.name)) { 131 | // continue; 132 | // } 133 | // if (!includedPaths.contains(package.path)) { 134 | // ohterPaths.add(package.path); 135 | // } 136 | // } 137 | // // ignore: empty_catches 138 | // } catch (e) {} 139 | // } 140 | 141 | // final AnalysisContextCollectionImpl contextCollection1 = 142 | // AnalysisContextCollectionImpl( 143 | // resourceProvider: resourceProvider, 144 | // includedPaths: [ 145 | // ...includedPaths, 146 | // ...ohterPaths, 147 | // ], 148 | // byteStore: createByteStore(), 149 | // sdkPath: _sdkPath, 150 | // fileContentCache: FileContentCache(resourceProvider), 151 | // ); 152 | // _unSolvedAccessibleExtensions.clear(); 153 | // for (final DriverBasedAnalysisContext context 154 | // in contextCollection1.contexts) { 155 | // // if (includedPaths.contains(context.root)) { 156 | // // continue; 157 | // // } 158 | // for (final String path in context.contextRoot.analyzedFiles()) { 159 | // if (!context.contextRoot.isAnalyzed(path)) { 160 | // continue; 161 | // } 162 | // await saveAccessibleExtensions( 163 | // analysisContext: context, 164 | // path: path, 165 | // solved: false, 166 | // ); 167 | // } 168 | // } 169 | // } 170 | 171 | // /// Handle a 'plugin.versionCheck' request. 172 | // /// 173 | // /// Throw a [RequestFailure] if the request could not be handled. 174 | // @override 175 | // Future handlePluginVersionCheck( 176 | // PluginVersionCheckParams parameters) async { 177 | // _sdkPath = parameters.sdkPath; 178 | // return super.handlePluginVersionCheck(parameters); 179 | // } 180 | 181 | // Map _parseDependencyTypes(String rootPackagePath) { 182 | // final File pubspecLock = 183 | // File(path_context.join(rootPackagePath, 'pubspec.lock')); 184 | // if (!pubspecLock.existsSync()) { 185 | // throw StateError( 186 | // 'Unable to generate package graph, no `pubspec.lock` found. ' 187 | // 'This program must be ran from the root directory of your package.'); 188 | // } 189 | // // dependency 190 | // final Map dependencyTypes = {}; 191 | // final YamlMap dependencies = 192 | // loadYaml(pubspecLock.readAsStringSync()) as YamlMap; 193 | 194 | // final YamlMap packages = dependencies['packages'] as YamlMap; 195 | // for (final dynamic packageName in packages.keys) { 196 | // final YamlMap package = packages[packageName] as YamlMap; 197 | // if (package['dependency'] == 'direct main') { 198 | // dependencyTypes[packageName.toString()] = 'direct main'; 199 | // } 200 | // } 201 | 202 | // return dependencyTypes; 203 | // } 204 | } 205 | -------------------------------------------------------------------------------- /lib/src/error/error/dart.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:analyzer/dart/ast/ast.dart'; 3 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 4 | import 'package:candies_analyzer_plugin/src/ignore_info.dart'; 5 | 6 | /// The cache error for get fixes 7 | class DartAnalysisError extends AnalysisError { 8 | DartAnalysisError( 9 | super.severity, 10 | super.type, 11 | super.location, 12 | super.message, 13 | super.code, { 14 | super.correction, 15 | super.url, 16 | super.contextMessages, 17 | super.hasFix, 18 | required this.astNode, 19 | required this.result, 20 | required this.ignoreInfo, 21 | }); 22 | 23 | /// The ast node to be used to quick fix 24 | /// astNode' location is not always equal to this.location 25 | /// for example, [PerferClassPrefix] 26 | /// astNode is ClassDeclaration 27 | /// but location is (astNode as ClassDeclaration).name2 28 | /// we need full astNode to get more info 29 | final AstNode astNode; 30 | 31 | /// The result of the file which this error is in. 32 | final ResolvedUnitResult result; 33 | 34 | /// The ignore info for file which this error is in. 35 | final CandiesAnalyzerPluginIgnoreInfo ignoreInfo; 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/error/error/generic.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 2 | 3 | /// The cache error for get fixes 4 | class GenericAnalysisError extends AnalysisError { 5 | GenericAnalysisError( 6 | super.severity, 7 | super.type, 8 | super.location, 9 | super.message, 10 | super.code, { 11 | super.correction, 12 | super.url, 13 | super.contextMessages, 14 | super.hasFix, 15 | required this.content, 16 | }); 17 | 18 | /// The file whole content 19 | final String content; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/error/error/yaml.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaml/yaml.dart'; 2 | 3 | import 'generic.dart'; 4 | 5 | /// The cache error for get fixes 6 | class YamlAnalysisError extends GenericAnalysisError { 7 | YamlAnalysisError( 8 | super.severity, 9 | super.type, 10 | super.location, 11 | super.message, 12 | super.code, { 13 | super.correction, 14 | super.url, 15 | super.contextMessages, 16 | super.hasFix, 17 | required super.content, 18 | required this.root, 19 | }); 20 | 21 | final YamlNode root; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/call_super_dispose.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implementation_imports 2 | 3 | import 'package:analyzer/dart/analysis/results.dart'; 4 | import 'package:analyzer/dart/ast/ast.dart'; 5 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 6 | import 'package:analyzer/source/source_range.dart'; 7 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 8 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 9 | import 'package:analyzer/src/dart/ast/ast.dart'; 10 | import 'package:candies_analyzer_plugin/src/config.dart'; 11 | import 'package:candies_analyzer_plugin/src/error/error/dart.dart'; 12 | 13 | import 'dart_lint.dart'; 14 | 15 | class MustCallSuperDispose extends DartLint with CallSuperDisposeMixin { 16 | @override 17 | String get code => 'must_call_super_dispose'; 18 | @override 19 | Future> getDartFixes( 20 | DartAnalysisError error, 21 | CandiesAnalyzerPluginConfig config, 22 | ) async { 23 | final ResolvedUnitResult resolvedUnitResult = error.result; 24 | final AstNode astNode = error.astNode; 25 | 26 | final AstNode? result = _matchLint(astNode as MethodDeclarationImpl); 27 | 28 | final Iterable cacheErrors = config 29 | .getCacheErrors(resolvedUnitResult.path, code: code) 30 | .whereType() 31 | .where((DartAnalysisError element) { 32 | final AstNode astNode = element.astNode; 33 | return _hasFix(astNode) && 34 | _matchLint(astNode as MethodDeclarationImpl) == astNode; 35 | }); 36 | 37 | return [ 38 | if (_hasFix(astNode) && result == astNode) 39 | await getDartFix( 40 | resolvedUnitResult: resolvedUnitResult, 41 | message: 'call super.dispose', 42 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 43 | _fix(dartFileEditBuilder, astNode); 44 | }, 45 | ), 46 | if (cacheErrors.length > 1) 47 | await getDartFix( 48 | resolvedUnitResult: resolvedUnitResult, 49 | message: 'call super.dispose where possible in file.', 50 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 51 | for (final DartAnalysisError error in cacheErrors) { 52 | _fix( 53 | dartFileEditBuilder, 54 | error.astNode as MethodDeclarationImpl, 55 | ); 56 | } 57 | }, 58 | ), 59 | ]; 60 | } 61 | } 62 | 63 | class EndCallSuperDispose extends DartLint with CallSuperDisposeMixin { 64 | @override 65 | String get code => 'end_call_super_dispose'; 66 | 67 | @override 68 | Future> getDartFixes( 69 | DartAnalysisError error, 70 | CandiesAnalyzerPluginConfig config, 71 | ) async { 72 | final ResolvedUnitResult resolvedUnitResult = error.result; 73 | final AstNode astNode = error.astNode; 74 | 75 | final AstNode? result = _matchLint(astNode as MethodDeclarationImpl); 76 | 77 | final Iterable cacheErrors = config 78 | .getCacheErrors(resolvedUnitResult.path, code: code) 79 | .whereType() 80 | .where((DartAnalysisError element) { 81 | final AstNode astNode = element.astNode; 82 | return _hasFix(astNode) && 83 | _matchLint(astNode as MethodDeclarationImpl) 84 | is ExpressionStatementImpl; 85 | }); 86 | 87 | return [ 88 | if (_hasFix(astNode) && result is ExpressionStatementImpl) 89 | await getDartFix( 90 | resolvedUnitResult: resolvedUnitResult, 91 | message: 'call super.dispose at the end of this method.', 92 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 93 | _fix1(dartFileEditBuilder, result, astNode); 94 | }, 95 | ), 96 | if (cacheErrors.length > 1) 97 | await getDartFix( 98 | resolvedUnitResult: resolvedUnitResult, 99 | message: 100 | 'call super.dispose at the end of this method where possible in file.', 101 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 102 | for (final DartAnalysisError error in cacheErrors) { 103 | _fix1( 104 | dartFileEditBuilder, 105 | _matchLint(error.astNode as MethodDeclarationImpl) 106 | as ExpressionStatementImpl, 107 | error.astNode as MethodDeclarationImpl, 108 | ); 109 | } 110 | }, 111 | ), 112 | ]; 113 | } 114 | } 115 | 116 | mixin CallSuperDisposeMixin on DartLint { 117 | @override 118 | String get message => 119 | 'Implementations of this method should end with a call to the inherited method, as in `super.dispose()`'; 120 | 121 | @override 122 | SyntacticEntity? matchLint(AstNode node) { 123 | if (node is MethodDeclarationImpl) { 124 | if (node.name2.toString() == 'dispose') { 125 | final AstNode? result = _matchLint(node); 126 | // remove the metadata 127 | if (result != null && 128 | result is MethodDeclarationImpl && 129 | result.metadata.isNotEmpty) { 130 | return _SyntacticEntity( 131 | result.firstTokenAfterCommentAndMetadata.offset, 132 | node.end, 133 | ); 134 | } 135 | 136 | return result; 137 | } 138 | } 139 | return null; 140 | } 141 | 142 | AstNode? _matchLint(MethodDeclarationImpl node) { 143 | bool callSuperDispose = false; 144 | for (final ChildEntity element in node.body.namedChildEntities) { 145 | if (element.value is BlockImpl) { 146 | final BlockImpl block = element.value as BlockImpl; 147 | final NodeListImpl statements = block.statements; 148 | // not call super.dispose 149 | if (this is MustCallSuperDispose && statements.isEmpty) { 150 | return node; 151 | } 152 | 153 | for (final Statement statement in statements) { 154 | if (statement is ExpressionStatementImpl && 155 | statement.expression.toString() == 'super.dispose()') { 156 | callSuperDispose = true; 157 | // not call super.dispose at the end of this method 158 | if (this is EndCallSuperDispose && statement != statements.last) { 159 | return statement; 160 | } 161 | } 162 | } 163 | } else if (element.value.toString() == 'super.dispose()') { 164 | callSuperDispose = true; 165 | } 166 | } 167 | // not call super.dispose 168 | if (this is MustCallSuperDispose && !callSuperDispose) { 169 | return node; 170 | } 171 | return null; 172 | } 173 | } 174 | 175 | void _fix1(DartFileEditBuilder dartFileEditBuilder, 176 | ExpressionStatementImpl result, MethodDeclarationImpl astNode) { 177 | dartFileEditBuilder.addSimpleReplacement( 178 | SourceRange( 179 | result.offset, 180 | result.length, 181 | ), 182 | ''); 183 | dartFileEditBuilder.addSimpleInsertion(astNode.end - 1, 'super.dispose();'); 184 | dartFileEditBuilder.format(SourceRange( 185 | astNode.offset, 186 | astNode.length, 187 | )); 188 | } 189 | 190 | void _fix( 191 | DartFileEditBuilder dartFileEditBuilder, MethodDeclarationImpl astNode) { 192 | dartFileEditBuilder.addSimpleInsertion(astNode.end - 1, 'super.dispose();'); 193 | dartFileEditBuilder.format( 194 | SourceRange( 195 | astNode.offset, 196 | astNode.length, 197 | ), 198 | ); 199 | } 200 | 201 | bool _hasFix(AstNode astNode) { 202 | return astNode is MethodDeclarationImpl && 203 | astNode.body is BlockFunctionBodyImpl; 204 | } 205 | 206 | class _SyntacticEntity extends SyntacticEntity { 207 | _SyntacticEntity(this.offset, this.end) : length = end - offset; 208 | @override 209 | final int offset; 210 | 211 | @override 212 | final int length; 213 | 214 | @override 215 | final int end; 216 | } 217 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/good_doc_comments.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implementation_imports 2 | 3 | import 'package:analyzer/dart/ast/ast.dart'; 4 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 5 | import 'package:analyzer/dart/ast/token.dart'; 6 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 7 | 8 | class GoodDocComments extends DartLint { 9 | @override 10 | String get code => 'good_doc_comments'; 11 | 12 | @override 13 | SyntacticEntity? matchLint(AstNode node) { 14 | CommentToken? precedingComments = node.beginToken.precedingComments; 15 | if (precedingComments == null && node.beginToken is CommentToken?) { 16 | precedingComments = node.beginToken as CommentToken?; 17 | } 18 | // SINGLE_LINE_COMMENT 19 | 20 | while (precedingComments != null) { 21 | if (precedingComments.type == TokenType.MULTI_LINE_COMMENT) { 22 | precedingComments = precedingComments.next as CommentToken?; 23 | continue; 24 | } 25 | 26 | //TokenType.SINGLE_LINE_COMMENT; 27 | // if (precedingComments is DartDocToken) { 28 | // } else 29 | 30 | final String value = precedingComments.lexeme; 31 | // => /// 32 | if (precedingComments is DocumentationCommentToken) { 33 | if ((!value.startsWith('/// ') && 34 | // single comment without content 35 | !(value == '///' && 36 | (precedingComments.previous != null || 37 | precedingComments.next != null))) || 38 | node.parent is Block) { 39 | return precedingComments; 40 | } 41 | } 42 | // is // 43 | else { 44 | if ( 45 | //node.parent is! Block || 46 | !value.startsWith('// ') && 47 | !(value == '//' && 48 | (precedingComments.previous != null || 49 | precedingComments.next != null))) { 50 | return precedingComments; 51 | } 52 | } 53 | 54 | precedingComments = precedingComments.next as CommentToken?; 55 | } 56 | return null; 57 | } 58 | 59 | @override 60 | String get message => 61 | 'wrong comments format. (/// xxx) for public api and (// xxx) for other cases.'; 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/perfer_class_prefix.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:analyzer/dart/ast/ast.dart'; 3 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 4 | import 'package:analyzer/dart/ast/token.dart'; 5 | import 'package:analyzer/dart/ast/visitor.dart'; 6 | import 'package:analyzer/source/source_range.dart'; 7 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 8 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 9 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 10 | 11 | /// The 'perfer_class_prefix' lint 12 | class PerferClassPrefix extends DartLint { 13 | PerferClassPrefix(this.prefix); 14 | 15 | final String prefix; 16 | 17 | @override 18 | String get code => 'perfer_${prefix}_class_prefix'; 19 | 20 | @override 21 | String? get url => 22 | 'https://github.com/fluttercandies/candies_analyzer_plugin'; 23 | 24 | @override 25 | SyntacticEntity? matchLint(AstNode node) { 26 | if (node is ClassDeclaration) { 27 | final String name = node.name2.toString(); 28 | final int startIndex = _getClassNameStartIndex(name); 29 | if (!name.substring(startIndex).startsWith(prefix)) { 30 | return node.name2; 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | @override 37 | String get message => 'Define a class name start with $prefix'; 38 | 39 | @override 40 | Future> getDartFixes( 41 | DartAnalysisError error, 42 | CandiesAnalyzerPluginConfig config, 43 | ) async { 44 | final ResolvedUnitResult resolvedUnitResult = error.result; 45 | 46 | final Iterable cacheErrors = config 47 | .getCacheErrors(resolvedUnitResult.path, code: code) 48 | .whereType(); 49 | 50 | final Map> references = 51 | _findClassReferences(cacheErrors, resolvedUnitResult); 52 | 53 | return [ 54 | await getDartFix( 55 | resolvedUnitResult: resolvedUnitResult, 56 | message: 'Use $prefix as a class prefix.', 57 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 58 | _fix( 59 | error, 60 | resolvedUnitResult, 61 | dartFileEditBuilder, 62 | references[error]!, 63 | ); 64 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 65 | }, 66 | ), 67 | if (cacheErrors.length > 1) 68 | await getDartFix( 69 | resolvedUnitResult: resolvedUnitResult, 70 | message: 'Use $prefix as a class prefix where possible in file.', 71 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 72 | for (final DartAnalysisError error in cacheErrors) { 73 | _fix( 74 | error, 75 | resolvedUnitResult, 76 | dartFileEditBuilder, 77 | references[error]!, 78 | ); 79 | } 80 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 81 | }, 82 | ), 83 | ]; 84 | } 85 | 86 | void _fix( 87 | DartAnalysisError error, 88 | ResolvedUnitResult resolvedUnitResult, 89 | DartFileEditBuilder dartFileEditBuilder, 90 | Set references, 91 | ) { 92 | final AstNode astNode = error.astNode; 93 | // get name node 94 | final Token nameNode = (astNode as ClassDeclaration).name2; 95 | final String nameString = nameNode.lexeme; 96 | 97 | final int startIndex = _getClassNameStartIndex(nameString); 98 | final String replace = 99 | '${nameString.substring(0, startIndex)}$prefix${nameString.substring(startIndex)}'; 100 | 101 | for (final SyntacticEntity match in references) { 102 | dartFileEditBuilder.addSimpleReplacement( 103 | SourceRange(match.offset, match.length), replace); 104 | } 105 | } 106 | 107 | Map> _findClassReferences( 108 | Iterable errors, 109 | ResolvedUnitResult resolvedUnitResult, 110 | ) { 111 | final Map> references = 112 | >{}; 113 | final Map classNames = 114 | {}; 115 | 116 | for (final DartAnalysisError error in errors) { 117 | classNames[(error.astNode as ClassDeclaration).name2.lexeme] = error; 118 | references[error] = {}; 119 | } 120 | 121 | resolvedUnitResult.unit 122 | .accept(_FindClassReferenceVisitor(references, classNames)); 123 | 124 | return references; 125 | } 126 | 127 | int _getClassNameStartIndex(String nameString) { 128 | int index = 0; 129 | while (nameString[index] == '_') { 130 | index++; 131 | if (index == nameString.length - 1) { 132 | break; 133 | } 134 | } 135 | return index; 136 | } 137 | } 138 | 139 | class _FindClassReferenceVisitor extends GeneralizingAstVisitor { 140 | _FindClassReferenceVisitor(this.references, this.classNames); 141 | final Map> references; 142 | final Map classNames; 143 | 144 | @override 145 | void visitNode(AstNode node) { 146 | if (node.childEntities.length == 1) { 147 | final String source = node.toSource(); 148 | if (classNames.keys.contains(source)) { 149 | references[classNames[source]]!.add(node); 150 | return; 151 | } 152 | } 153 | super.visitNode(node); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/perfer_doc_comments.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 3 | import 'package:analyzer/dart/ast/token.dart'; 4 | import 'package:analyzer/dart/ast/visitor.dart'; 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:candies_analyzer_plugin/src/error/lints/dart/util/ast.dart' 7 | as ast; 8 | 9 | import 'dart_lint.dart'; 10 | 11 | /// The 'perfer_doc_comments' lint 12 | /// https://dart.dev/guides/language/effective-dart/documentation 13 | /// The same like public_member_api_docs 14 | /// but we can ignore lint or ignore file by override [ignoreLint] and [ignoreFile] 15 | /// and you can override [isPrivate] and [inPrivateMember] to filter lint 16 | class PerferDocComments extends DartLint { 17 | @override 18 | String get code => 'perfer_doc_comments'; 19 | 20 | @override 21 | String? get url => 22 | 'https://github.com/fluttercandies/candies_analyzer_plugin'; 23 | 24 | @override 25 | AstVisitor get astVisitor => _Visitor(this); 26 | 27 | @override 28 | SyntacticEntity? matchLint(AstNode node) => null; 29 | 30 | /// DON’T use block comments for documentation 31 | /// block comments /* Assume we have a valid name. */ 32 | /// doc comments to document members and types 33 | /// DO use /// doc comments to document members and types. 34 | /// DO put doc comments before metadata annotations. 35 | @override 36 | String get message => 37 | 'DO use /// doc comments to document members and types.'; 38 | 39 | /// Returns `true` if this [node] is the child of a private compilation unit 40 | /// member. 41 | bool inPrivateMember(AstNode node) { 42 | final AstNode? parent = node.parent; 43 | if (parent is NamedCompilationUnitMember) { 44 | return isPrivate(parent.name2, parent); 45 | } 46 | if (parent is ExtensionDeclaration) { 47 | return parent.name2 == null || isPrivate(parent.name2, parent); 48 | } 49 | return false; 50 | } 51 | 52 | /// Check if the given identifier has a private name. 53 | bool isPrivate(Token? name, AstNode parent) { 54 | return ast.isPrivate(name); 55 | } 56 | 57 | /// Check whether it's valid comment 58 | bool isValidDocumentationComment(Declaration node) => 59 | node.documentationComment != null; 60 | 61 | @override 62 | bool ignoreLint(SyntacticEntity offset, AstNode node) { 63 | if (node is FunctionDeclaration && offset.toString() == 'main') { 64 | return true; 65 | } 66 | return super.ignoreLint(offset, node); 67 | } 68 | } 69 | 70 | class _Visitor extends SimpleAstVisitor { 71 | _Visitor(this.dartLint); 72 | final PerferDocComments dartLint; 73 | SyntacticEntity? check(Declaration node) { 74 | if (!dartLint.isValidDocumentationComment(node) && 75 | !isOverridingMember(node)) { 76 | final SyntacticEntity errorNode = ast.getNodeToAnnotate(node); 77 | dartLint.reportLint(errorNode, node); 78 | return errorNode; 79 | } 80 | return null; 81 | } 82 | 83 | void checkMethods(List members) { 84 | // Check methods 85 | 86 | final Map getters = 87 | {}; 88 | final List setters = []; 89 | 90 | // Non-getters/setters. 91 | final List methods = []; 92 | 93 | // Identify getter/setter pairs. 94 | for (final ClassMember member in members) { 95 | if (member is MethodDeclaration && 96 | !dartLint.isPrivate(member.name2, member)) { 97 | if (member.isGetter) { 98 | getters[member.name2.lexeme] = member; 99 | } else if (member.isSetter) { 100 | setters.add(member); 101 | } else { 102 | methods.add(member); 103 | } 104 | } 105 | } 106 | 107 | // Check all getters, and collect offenders along the way. 108 | final Set missingDocs = {}; 109 | for (final MethodDeclaration getter in getters.values) { 110 | if (check(getter) != null) { 111 | missingDocs.add(getter); 112 | } 113 | } 114 | 115 | // But only setters whose getter is missing a doc. 116 | for (final MethodDeclaration setter in setters) { 117 | final MethodDeclaration? getter = getters[setter.name2.lexeme]; 118 | if (getter != null && missingDocs.contains(getter)) { 119 | check(setter); 120 | } 121 | } 122 | 123 | // Check remaining methods. 124 | methods.forEach(check); 125 | } 126 | 127 | Element? getOverriddenMember(Element? member) { 128 | if (member == null) { 129 | return null; 130 | } 131 | 132 | final InterfaceElement? interfaceElement = 133 | member.thisOrAncestorOfType(); 134 | if (interfaceElement == null) { 135 | return null; 136 | } 137 | final String? name = member.name; 138 | if (name == null) { 139 | return null; 140 | } 141 | 142 | for (final ElementAnnotation annotation in member.metadata) { 143 | if (annotation.isOverride) { 144 | return annotation.element; 145 | } 146 | } 147 | 148 | return null; 149 | // final Uri libraryUri = interfaceElement.library.source.uri; 150 | // return context.inheritanceManager.getInherited( 151 | // interfaceElement.thisType, 152 | // Name(libraryUri, name), 153 | // ); 154 | } 155 | 156 | bool isOverridingMember(Declaration node) => 157 | getOverriddenMember(node.declaredElement2) != null; 158 | 159 | @override 160 | void visitClassDeclaration(ClassDeclaration node) { 161 | _visitMembers(node, node.name2, node.members); 162 | node.visitChildren(this); 163 | } 164 | 165 | @override 166 | void visitClassTypeAlias(ClassTypeAlias node) { 167 | if (!dartLint.isPrivate(node.name2, node)) { 168 | check(node); 169 | } 170 | node.visitChildren(this); 171 | } 172 | 173 | @override 174 | void visitCompilationUnit(CompilationUnit node) { 175 | final Map getters = 176 | {}; 177 | final List setters = []; 178 | 179 | // Check functions. 180 | 181 | // Non-getters/setters. 182 | final List functions = []; 183 | 184 | // Identify getter/setter pairs. 185 | for (final CompilationUnitMember member in node.declarations) { 186 | if (member is FunctionDeclaration) { 187 | final Token name = member.name2; 188 | if (!dartLint.isPrivate(name, member) && name.lexeme != 'main') { 189 | if (member.isGetter) { 190 | getters[member.name2.lexeme] = member; 191 | } else if (member.isSetter) { 192 | setters.add(member); 193 | } else { 194 | functions.add(member); 195 | } 196 | } 197 | } 198 | } 199 | 200 | // Check all getters, and collect offenders along the way. 201 | final Set missingDocs = {}; 202 | for (final FunctionDeclaration getter in getters.values) { 203 | if (check(getter) != null) { 204 | missingDocs.add(getter); 205 | } 206 | } 207 | 208 | // But only setters whose getter is missing a doc. 209 | for (final FunctionDeclaration setter in setters) { 210 | final FunctionDeclaration? getter = getters[setter.name2.lexeme]; 211 | if (getter != null && missingDocs.contains(getter)) { 212 | check(setter); 213 | } 214 | } 215 | 216 | // Check remaining functions. 217 | functions.forEach(check); 218 | 219 | node.visitChildren(this); 220 | } 221 | 222 | @override 223 | void visitConstructorDeclaration(ConstructorDeclaration node) { 224 | if (!dartLint.inPrivateMember(node) && 225 | !dartLint.isPrivate(node.name2, node)) { 226 | check(node); 227 | } 228 | } 229 | 230 | @override 231 | void visitEnumConstantDeclaration(EnumConstantDeclaration node) { 232 | if (!dartLint.inPrivateMember(node) && 233 | !dartLint.isPrivate(node.name2, node)) { 234 | check(node); 235 | } 236 | node.visitChildren(this); 237 | } 238 | 239 | @override 240 | void visitEnumDeclaration(EnumDeclaration node) { 241 | if (dartLint.isPrivate(node.name2, node)) { 242 | return; 243 | } 244 | 245 | check(node); 246 | checkMethods(node.members); 247 | node.visitChildren(this); 248 | } 249 | 250 | @override 251 | void visitExtensionDeclaration(ExtensionDeclaration node) { 252 | if (node.name2 == null || dartLint.isPrivate(node.name2, node)) { 253 | return; 254 | } 255 | 256 | check(node); 257 | checkMethods(node.members); 258 | node.visitChildren(this); 259 | } 260 | 261 | @override 262 | void visitFieldDeclaration(FieldDeclaration node) { 263 | if (!dartLint.inPrivateMember(node)) { 264 | for (final VariableDeclaration field in node.fields.variables) { 265 | if (!dartLint.isPrivate(field.name2, node)) { 266 | check(field); 267 | } 268 | } 269 | } 270 | } 271 | 272 | @override 273 | void visitFunctionTypeAlias(FunctionTypeAlias node) { 274 | if (!dartLint.isPrivate(node.name2, node)) { 275 | check(node); 276 | } 277 | } 278 | 279 | @override 280 | void visitGenericTypeAlias(GenericTypeAlias node) { 281 | if (!dartLint.isPrivate(node.name2, node)) { 282 | check(node); 283 | } 284 | } 285 | 286 | @override 287 | void visitMixinDeclaration(MixinDeclaration node) { 288 | _visitMembers(node, node.name2, node.members); 289 | node.visitChildren(this); 290 | } 291 | 292 | @override 293 | void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { 294 | for (final VariableDeclaration decl in node.variables.variables) { 295 | if (!dartLint.isPrivate(decl.name2, node)) { 296 | check(decl); 297 | } 298 | } 299 | } 300 | 301 | @override 302 | void visitFunctionDeclaration(FunctionDeclaration node) { 303 | if (!dartLint.isPrivate(node.name2, node)) { 304 | check(node); 305 | } 306 | } 307 | 308 | void _visitMembers(Declaration node, Token name, List members) { 309 | if (dartLint.isPrivate(name, node)) { 310 | return; 311 | } 312 | 313 | check(node); 314 | checkMethods(members); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/perfer_singleton.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implementation_imports 2 | 3 | import 'package:analyzer/dart/ast/ast.dart'; 4 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 5 | import 'package:analyzer/dart/ast/token.dart'; 6 | import 'package:analyzer/dart/element/element.dart'; 7 | import 'package:analyzer/dart/element/type.dart'; 8 | import 'package:analyzer/src/dart/ast/ast.dart'; 9 | import 'package:analyzer/src/dart/element/element.dart'; 10 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 11 | 12 | class PreferSingleton extends DartLint { 13 | @override 14 | String get code => 'prefer_singleton'; 15 | 16 | @override 17 | SyntacticEntity? matchLint(AstNode node) { 18 | if (node is InstanceCreationExpressionImpl && 19 | (node.parent is PropertyAccess || node.parent is MethodInvocation)) { 20 | final DartType? type = node.staticType; 21 | final Element? element = type?.element2; 22 | final LibraryElement? library = element?.library; 23 | if (node.keyword?.type != Keyword.CONST && 24 | library != null && 25 | !library.isInSdk && 26 | !library.isInFlutterSdk) { 27 | if (element != null && element is ClassElementImpl) { 28 | bool hasSingleton = false; 29 | for (final ConstructorElement ctor in element.constructors) { 30 | if (ctor.isDefaultConstructor && 31 | ctor.isFactory && 32 | ctor.isPublic && 33 | !ctor.isGenerative) { 34 | hasSingleton = true; 35 | break; 36 | } 37 | } 38 | if (!hasSingleton) { 39 | return node; 40 | } 41 | } 42 | } 43 | } 44 | return null; 45 | } 46 | 47 | @override 48 | String get message => 'This is not a singleton, and new Object every time.'; 49 | 50 | @override 51 | String? get correction => 'use as a singleton or use as const ctor'; 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/prefer_asset_const.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 3 | 4 | import 'dart_lint.dart'; 5 | 6 | /// The 'prefer_asset_const' lint 7 | class PreferAssetConst extends DartLint { 8 | @override 9 | String get code => 'prefer_asset_const'; 10 | 11 | @override 12 | String? get url => 'https://pub.dev/packages/assets_generator'; 13 | 14 | @override 15 | String get message => 'Prefer to use asset const instead of a string.'; 16 | 17 | @override 18 | String? get correction => 19 | 'Click \'prefer_asset_const\' to show how to generate asset const automatically.'; 20 | 21 | bool _isString(ArgumentList argumentList) { 22 | for (final Expression argument in argumentList.arguments) { 23 | final String argumentString = argument.toString(); 24 | return argumentString.startsWith('\'') && argumentString.endsWith('\''); 25 | } 26 | return false; 27 | } 28 | 29 | @override 30 | SyntacticEntity? matchLint(AstNode node) { 31 | if (node is MethodInvocation) { 32 | final String astNodeString = node.toString(); 33 | if (astNodeString.startsWith('rootBundle.load') && 34 | _isString(node.argumentList)) { 35 | return node; 36 | } 37 | } else if (node is InstanceCreationExpression) { 38 | final String astNodeString = node.toString(); 39 | if ((astNodeString.startsWith('AssetImage(') || 40 | astNodeString.startsWith('Image.asset(')) && 41 | _isString(node.argumentList)) { 42 | return node; 43 | } 44 | } 45 | 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/prefer_named_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 3 | 4 | import 'dart_lint.dart'; 5 | 6 | /// The 'prefer_named_routes' lint 7 | class PreferNamedRoutes extends DartLint { 8 | @override 9 | String get code => 'prefer_named_routes'; 10 | 11 | @override 12 | String get message => 'Prefer to use named routes.'; 13 | 14 | @override 15 | String? get correction => 16 | 'Click \'prefer_named_routes\' to show how to generate named routes const automatically.'; 17 | 18 | @override 19 | String? get url => 'https://pub.dev/packages/ff_annotation_route'; 20 | 21 | @override 22 | SyntacticEntity? matchLint(AstNode node) { 23 | if (node is MethodInvocation) { 24 | final String methodName = node.methodName.toString(); 25 | final String nodeString = node.toString(); 26 | if ((nodeString.startsWith('Navigator.') || 27 | nodeString.contains('MaterialPageRoute') || 28 | nodeString.contains('CupertinoPageRoute')) && 29 | (methodName.toLowerCase().contains('push') && 30 | !methodName.contains('Named'))) { 31 | return node; 32 | } 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/prefer_safe_set_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:analyzer/dart/ast/ast.dart'; 3 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 4 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 5 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 6 | import 'package:candies_analyzer_plugin/src/config.dart'; 7 | import 'package:candies_analyzer_plugin/src/error/error/dart.dart'; 8 | import 'package:candies_analyzer_plugin/src/extension.dart'; 9 | 10 | import 'dart_lint.dart'; 11 | 12 | /// The 'prefer_safe_setState' lint 13 | class PerferSafeSetState extends DartLint { 14 | @override 15 | String get code => 'prefer_safe_setState'; 16 | 17 | @override 18 | String get message => 'Prefer to check mounted before setState'; 19 | 20 | @override 21 | String? get correction => 'Add if(mounted){} for setState'; 22 | 23 | @override 24 | String? get url => 25 | 'https://github.com/fluttercandies/candies_analyzer_plugin'; 26 | 27 | @override 28 | Future> getDartFixes( 29 | DartAnalysisError error, 30 | CandiesAnalyzerPluginConfig config, 31 | ) async { 32 | final ResolvedUnitResult resolvedUnitResult = error.result; 33 | final Iterable cacheErrors = config 34 | .getCacheErrors(resolvedUnitResult.path, code: code) 35 | .whereType() 36 | .where((DartAnalysisError element) => _hasFix(element.astNode)); 37 | 38 | final AstNode astNode = error.astNode; 39 | return [ 40 | if (_hasFix(astNode)) 41 | await getDartFix( 42 | resolvedUnitResult: resolvedUnitResult, 43 | message: 'Add if(mounted){} for setState', 44 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 45 | _fix(error, dartFileEditBuilder); 46 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 47 | }, 48 | ), 49 | if (cacheErrors.length > 1) 50 | await getDartFix( 51 | resolvedUnitResult: resolvedUnitResult, 52 | message: 'Add if(mounted){} for setState where possible in file.', 53 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 54 | for (final DartAnalysisError error in cacheErrors) { 55 | _fix(error, dartFileEditBuilder); 56 | } 57 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 58 | }, 59 | ), 60 | ]; 61 | } 62 | 63 | bool _hasFix(AstNode astNode) { 64 | return astNode.toString().endsWith(';') || 65 | (astNode.parent != null && astNode.parent!.toString().endsWith(';')); 66 | } 67 | 68 | void _fix(DartAnalysisError error, DartFileEditBuilder dartFileEditBuilder) { 69 | final AstNode astNode = error.astNode; 70 | final int start = astNode.offset; 71 | final int end = astNode.end + 1; 72 | const String a = 'if(mounted){'; 73 | const String b = '}'; 74 | dartFileEditBuilder.addSimpleInsertion(start, a); 75 | dartFileEditBuilder.addSimpleInsertion(end, b); 76 | } 77 | 78 | @override 79 | SyntacticEntity? matchLint(AstNode node) { 80 | if (node is MethodInvocation && node.methodName.toString() == 'setState') { 81 | AstNode? parent = node.parent; 82 | while (parent != null && parent is! BlockFunctionBody) { 83 | if (parent is IfStatement && 84 | parent.condition.toString().contains('mounted')) { 85 | return null; 86 | } else if (parent is ClassDeclaration) { 87 | return null; 88 | } 89 | parent = parent.parent; 90 | } 91 | return node; 92 | } 93 | return null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/prefer_trailing_comma.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:analyzer/dart/ast/ast.dart'; 3 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 4 | 5 | import 'package:analyzer/dart/ast/token.dart'; 6 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 7 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 8 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 9 | 10 | class PreferTrailingComma extends DartLint { 11 | @override 12 | String get code => 'prefer_trailing_comma'; 13 | 14 | @override 15 | SyntacticEntity? matchLint(AstNode node) { 16 | if (node is ArgumentList) { 17 | return _handleNodes( 18 | node.arguments, 19 | node.leftParenthesis, 20 | node.rightParenthesis, 21 | ); 22 | } else if (node is FormalParameterList) { 23 | return _handleNodes( 24 | node.parameters, 25 | node.leftParenthesis, 26 | node.rightParenthesis, 27 | ); 28 | } else if (node is ListLiteral) { 29 | return _handleNodes( 30 | node.elements, 31 | node.leftBracket, 32 | node.rightBracket, 33 | ); 34 | } else if (node is SetOrMapLiteral) { 35 | return _handleNodes( 36 | node.elements, 37 | node.leftBracket, 38 | node.rightBracket, 39 | ); 40 | } 41 | 42 | return null; 43 | } 44 | 45 | @override 46 | String get message => 'Prefer trailing comma.'; 47 | 48 | SyntacticEntity? _handleNodes( 49 | Iterable nodes, 50 | Token leftBracket, 51 | Token rightBracket, 52 | ) { 53 | if (nodes.isEmpty) { 54 | return null; 55 | } 56 | 57 | final AstNode last = nodes.last; 58 | final LineInfo lineInfo = (last.root as CompilationUnit).lineInfo; 59 | 60 | // not end with comma 61 | if (last.endToken.next?.type != TokenType.COMMA) { 62 | final int startLineNumber = leftBracket.startLineNumber(lineInfo); 63 | final int endLineNumber = rightBracket.startLineNumber(lineInfo); 64 | 65 | if (startLineNumber != endLineNumber && 66 | // it's not in the same line 67 | !(last.startLineNumber(lineInfo) == startLineNumber && 68 | last.endLineNumber(lineInfo) == endLineNumber)) { 69 | return last; 70 | } 71 | } 72 | 73 | return null; 74 | } 75 | 76 | @override 77 | Future> getDartFixes( 78 | DartAnalysisError error, 79 | CandiesAnalyzerPluginConfig config, 80 | ) async { 81 | final ResolvedUnitResult resolvedUnitResult = error.result; 82 | 83 | final Iterable cacheErrors = config 84 | .getCacheErrors(resolvedUnitResult.path, code: code) 85 | .whereType(); 86 | 87 | return [ 88 | await getDartFix( 89 | resolvedUnitResult: resolvedUnitResult, 90 | message: 'Add trailing comma.', 91 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 92 | _fix(dartFileEditBuilder, error); 93 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 94 | }, 95 | ), 96 | if (cacheErrors.length > 1) 97 | await getDartFix( 98 | resolvedUnitResult: resolvedUnitResult, 99 | message: 'Add trailing comma where possible in file.', 100 | buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) { 101 | for (final DartAnalysisError error in cacheErrors) { 102 | _fix(dartFileEditBuilder, error); 103 | } 104 | dartFileEditBuilder.formatAll(resolvedUnitResult.unit); 105 | }, 106 | ), 107 | ]; 108 | } 109 | 110 | void _fix(DartFileEditBuilder dartFileEditBuilder, DartAnalysisError error) { 111 | dartFileEditBuilder.addSimpleInsertion( 112 | error.location.offset + error.location.length, 113 | ',', 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/unused_code.dart: -------------------------------------------------------------------------------- 1 | // void findReference( 2 | // Reference? reference, 3 | // bool Function(Element element) find, 4 | // Set caches, { 5 | // String? path, 6 | // }) { 7 | // if (reference == null) { 8 | // return; 9 | // } 10 | // if (caches.contains(reference)) { 11 | // return; 12 | // } 13 | 14 | // caches.add(reference); 15 | 16 | // final Element? element = reference.element; 17 | 18 | // // not find reference not in path file 19 | // if (path != null && element != null && element.source?.fullName != path) { 20 | // return; 21 | // } 22 | 23 | // if (element != null) { 24 | // find(element); 25 | // } 26 | 27 | // for (final Reference child in reference.children) { 28 | // findReference(child, find, caches, path: path); 29 | // } 30 | 31 | // findReference(reference.parent, find, caches, path: path); 32 | // } 33 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/util/analyzer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: implementation_imports 6 | 7 | import 'dart:convert'; 8 | import 'dart:io'; 9 | import 'dart:math' as math; 10 | 11 | import 'package:analyzer/error/error.dart'; 12 | import 'package:analyzer/src/generated/engine.dart' show AnalysisErrorInfo; 13 | import 'package:analyzer/src/lint/io.dart' show errorSink; 14 | import 'package:analyzer/src/lint/linter.dart'; 15 | import 'package:analyzer/src/lint/registry.dart'; 16 | import 'package:analyzer/src/lint/util.dart' as util; 17 | 18 | //import 'version.dart'; 19 | 20 | export 'package:analyzer/dart/element/type_system.dart'; 21 | export 'package:analyzer/src/dart/ast/token.dart'; 22 | export 'package:analyzer/src/dart/element/inheritance_manager3.dart' 23 | show InheritanceManager3, Name; 24 | export 'package:analyzer/src/dart/error/lint_codes.dart'; 25 | export 'package:analyzer/src/dart/resolver/exit_detector.dart'; 26 | export 'package:analyzer/src/generated/engine.dart' show AnalysisErrorInfo; 27 | export 'package:analyzer/src/generated/source.dart' show LineInfo, Source; 28 | export 'package:analyzer/src/lint/linter.dart' 29 | show 30 | DartLinter, 31 | Group, 32 | LintFilter, 33 | LintRule, 34 | LinterContext, 35 | LinterOptions, 36 | Maturity, 37 | NodeLintRegistry, 38 | NodeLintRule; 39 | export 'package:analyzer/src/lint/pub.dart' show PSEntry, PubspecVisitor; 40 | export 'package:analyzer/src/lint/util.dart'; 41 | export 'package:analyzer/src/services/lint.dart' show lintRegistry; 42 | export 'package:analyzer/src/workspace/pub.dart' show PubWorkspacePackage; 43 | 44 | const int loggedAnalyzerErrorExitCode = 63; 45 | 46 | Future> lintFiles( 47 | DartLinter linter, List filesToLint) async { 48 | // Setup an error watcher to track whether an error was logged to stderr so 49 | // we can set the exit code accordingly. 50 | final ErrorWatchingSink errorWatcher = ErrorWatchingSink(errorSink); 51 | errorSink = errorWatcher; 52 | 53 | final Iterable errors = 54 | await linter.lintFiles(filesToLint); 55 | if (errorWatcher.encounteredError) { 56 | exitCode = loggedAnalyzerErrorExitCode; 57 | } else if (errors.isNotEmpty) { 58 | exitCode = _maxSeverity(errors.toList(), linter.options.filter); 59 | } 60 | 61 | return errors; 62 | } 63 | 64 | Iterable _filtered( 65 | List errors, LintFilter? filter) => 66 | (filter == null) 67 | ? errors 68 | : errors.where((AnalysisError e) => !filter.filter(e)); 69 | 70 | int _maxSeverity(List errors, LintFilter? filter) { 71 | int max = 0; 72 | for (final AnalysisErrorInfo info in errors) { 73 | _filtered(info.errors, filter).forEach((AnalysisError e) { 74 | max = math.max(max, e.errorCode.errorSeverity.ordinal); 75 | }); 76 | } 77 | return max; 78 | } 79 | 80 | /// Facade for managing access to `analyzer` package APIs. 81 | class Analyzer { 82 | /// Shared instance. 83 | static Analyzer facade = Analyzer(); 84 | 85 | /// Returns currently registered lint rules. 86 | Iterable get registeredRules => Registry.ruleRegistry; 87 | 88 | /// Cache linter version; used in summary signatures. 89 | void cacheLinterVersion() { 90 | //lint_service.linterVersion = version; 91 | } 92 | 93 | /// Create a library name prefix based on [libraryPath], [projectRoot] and 94 | /// current [packageName]. 95 | String createLibraryNamePrefix( 96 | {required String libraryPath, 97 | String? projectRoot, 98 | String? packageName}) => 99 | util.createLibraryNamePrefix( 100 | libraryPath: libraryPath, 101 | projectRoot: projectRoot, 102 | packageName: packageName); 103 | 104 | /// Register this [lint] with the analyzer's rule registry. 105 | void register(LintRule lint) { 106 | Registry.ruleRegistry.register(lint); 107 | } 108 | } 109 | 110 | class ErrorWatchingSink implements IOSink { 111 | ErrorWatchingSink(this.delegate); 112 | bool encounteredError = false; 113 | 114 | IOSink delegate; 115 | 116 | @override 117 | Future get done => delegate.done; 118 | 119 | @override 120 | Encoding get encoding => delegate.encoding; 121 | 122 | @override 123 | set encoding(Encoding encoding) { 124 | delegate.encoding = encoding; 125 | } 126 | 127 | @override 128 | void add(List data) { 129 | delegate.add(data); 130 | } 131 | 132 | @override 133 | void addError(Object error, [StackTrace? stackTrace]) { 134 | encounteredError = true; 135 | delegate.addError(error, stackTrace); 136 | } 137 | 138 | @override 139 | Future addStream(Stream> stream) => 140 | delegate.addStream(stream); 141 | 142 | @override 143 | Future close() => delegate.close(); 144 | 145 | @override 146 | Future flush() => delegate.flush(); 147 | 148 | @override 149 | void write(Object? obj) { 150 | delegate.write(obj); 151 | } 152 | 153 | @override 154 | void writeAll(Iterable objects, [String separator = '']) { 155 | delegate.writeAll(objects, separator); 156 | } 157 | 158 | @override 159 | void writeCharCode(int charCode) { 160 | delegate.writeCharCode(charCode); 161 | } 162 | 163 | @override 164 | void writeln([Object? obj = '']) { 165 | // 'Exception while using a Visitor to visit ...' ( 166 | if (obj.toString().startsWith('Exception')) { 167 | encounteredError = true; 168 | } 169 | delegate.writeln(obj); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/src/error/lints/dart/util/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'ast.dart'; 6 | 7 | // An identifier here is defined as: 8 | // * A sequence of `_`, `$`, letters or digits, 9 | // * where no `$` comes after a digit. 10 | final RegExp _identifier = 11 | RegExp(r'^[_$a-z]+(\d[_a-z\d]*)?$', caseSensitive: false); 12 | 13 | // A lower camel-case here is defined as: 14 | // * Any number of optional leading underscores, 15 | // * a lower case letter, `$` or `?` followed by a "word-tail" 16 | // (a sequence of lower-case letters, digits, `$` or `?`), 17 | // * followed by any number of either 18 | // * an upper case letter followed by a word tail, or 19 | // * an underscore and then a digit followed by a word tail. 20 | // * and potentially ended by a single optional underscore. 21 | final RegExp _lowerCamelCase = 22 | RegExp(r'^_*[?$a-z][a-z\d?$]*(?:(?:[A-Z]|_\d)[a-z\d?$]*)*_?$'); 23 | 24 | // A lower-case underscore (snake-case) is here defined as: 25 | // * A sequence of lower-case letters, digits and underscores, 26 | // * starting with a lower-case letter, and 27 | // * with no two adjacent underscores, 28 | // * and not ending in an underscore. 29 | final RegExp _lowerCaseUnderScore = RegExp(r'^[a-z](?:_?[a-z\d])*$'); 30 | 31 | @Deprecated('Prefer: ascii_utils.isValidFileName') 32 | final RegExp _lowerCaseUnderScoreWithDots = 33 | RegExp(r'^_?[_a-z\d]*(?:\.[a-z][_a-z\d]*)*$'); 34 | 35 | // A lower-case underscored (snake-case) with leading underscores is defined as 36 | // * An optional leading sequence of any number of underscores, 37 | // * followed by a sequence of lower-case letters, digits and underscores, 38 | // * with no two adjacent underscores, 39 | // * and not ending in an underscore. 40 | final RegExp _lowerCaseUnderScoreWithLeadingUnderscores = 41 | RegExp(r'^_*[a-z](?:_?[a-z\d])*$'); 42 | 43 | final RegExp _pubspec = RegExp(r'^_?pubspec\.yaml$'); 44 | 45 | // A library prefix here is defined as: 46 | // * An optional leading `?`, 47 | // * then any number of underscores, `_`, 48 | // * then a lower-case letter, 49 | // * followed by any number of lower-case letters, digits and underscores. 50 | final RegExp _validLibraryPrefix = RegExp(r'^\$?_*[a-z][_a-z\d]*$'); 51 | 52 | /// Returns `true` if the given [name] has a leading `_`. 53 | bool hasLeadingUnderscore(String name) => name.startsWith('_'); 54 | 55 | /// Check if this [string] is formatted in `CamelCase`. 56 | bool isCamelCase(String string) => CamelCaseString.isCamelCase(string); 57 | 58 | /// Returns `true` if this [fileName] is a Dart file. 59 | bool isDartFileName(String fileName) => fileName.endsWith('.dart'); 60 | 61 | /// Returns `true` if this [name] is a legal Dart identifier. 62 | bool isIdentifier(String name) => _identifier.hasMatch(name); 63 | 64 | /// Returns `true` if this [id] is `lowerCamelCase`. 65 | bool isLowerCamelCase(String id) => 66 | id.length == 1 && isUpperCase(id.codeUnitAt(0)) || 67 | id == '_' || 68 | _lowerCamelCase.hasMatch(id); 69 | 70 | /// Returns `true` if this [id] is `lower_camel_case_with_underscores`. 71 | bool isLowerCaseUnderScore(String id) => _lowerCaseUnderScore.hasMatch(id); 72 | 73 | /// Returns `true` if this [id] is `lower_camel_case_with_underscores_or.dots`. 74 | bool isLowerCaseUnderScoreWithDots(String id) => 75 | // ignore: deprecated_member_use_from_same_package 76 | _lowerCaseUnderScoreWithDots.hasMatch(id); 77 | 78 | /// Returns `true` if this [fileName] is a Pubspec file. 79 | bool isPubspecFileName(String fileName) => _pubspec.hasMatch(fileName); 80 | 81 | /// Returns `true` if the given code unit [c] is upper case. 82 | bool isUpperCase(int c) => c >= 0x40 && c <= 0x5A; 83 | 84 | /// Returns true if this [libraryPrefix] is valid. 85 | bool isValidLibraryPrefix(String libraryPrefix) => 86 | _validLibraryPrefix.hasMatch(libraryPrefix); 87 | 88 | /// Returns true if this [id] is a valid package name. 89 | bool isValidPackageName(String id) => 90 | _lowerCaseUnderScoreWithLeadingUnderscores.hasMatch(id) && 91 | isIdentifier(id) && 92 | !isReservedWord(id); 93 | 94 | class CamelCaseString { 95 | CamelCaseString(this.value) { 96 | if (!isCamelCase(value)) { 97 | throw ArgumentError.value(value, 'value', '$value is not CamelCase'); 98 | } 99 | } 100 | static final RegExp _camelCaseMatcher = RegExp(r'[A-Z][a-z]*'); 101 | 102 | // A camel case string here is defined as: 103 | // * An arbitrary number of optional leading `_`s or `$`s, 104 | // * followed by an upper-case letter, `$` or `?`, 105 | // * followed by any number of letters, digits, `?` or `$`s. 106 | // 107 | // This ensures that the text contains a `$`, `?` or upper-case letter 108 | // before any lower-case letter or digit, and no letters or `?`s before an 109 | // `_`. 110 | static final RegExp _camelCaseTester = 111 | RegExp(r'^_*(?:\$+_+)*[$?A-Z][$?a-zA-Z\d]*$'); 112 | 113 | final String value; 114 | String get humanized => _humanize(value); 115 | 116 | @override 117 | String toString() => value; 118 | 119 | static bool isCamelCase(String name) => _camelCaseTester.hasMatch(name); 120 | 121 | static String _humanize(String camelCase) => _camelCaseMatcher 122 | .allMatches(camelCase) 123 | .map((RegExpMatch m) => m[0]) 124 | .join(' '); 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/error/lints/generic_lint.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 2 | import 'package:analyzer/source/line_info.dart'; 3 | import 'package:analyzer/source/source_range.dart'; 4 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 5 | import 'package:analyzer_plugin/protocol/protocol_generated.dart'; 6 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; 7 | import 'package:candies_analyzer_plugin/src/config.dart'; 8 | import 'package:candies_analyzer_plugin/src/error/error/generic.dart'; 9 | import 'package:candies_analyzer_plugin/src/extension.dart'; 10 | import 'package:candies_analyzer_plugin/src/log.dart'; 11 | import 'package:path/path.dart' as path_package; 12 | 13 | import 'lint.dart'; 14 | 15 | /// The generic lint base 16 | abstract class GenericLint extends CandyLint { 17 | Iterable toGenericAnalysisErrors({ 18 | required AnalysisContext analysisContext, 19 | required String path, 20 | required CandiesAnalyzerPluginConfig config, 21 | required String content, 22 | required LineInfo lineInfo, 23 | }) sync* { 24 | final List nodes = matchLint( 25 | content, 26 | path, 27 | lineInfo, 28 | ).toList(); 29 | 30 | CandiesAnalyzerPluginLogger().log( 31 | 'find ${nodes.length} yaml lint($code) at $path', 32 | root: analysisContext.root, 33 | ); 34 | 35 | final List errors = []; 36 | _cacheErrorsForFixes[path] = errors; 37 | for (final SourceRange node in nodes) { 38 | final Location location = sourceSpanToLocation( 39 | path, 40 | node, 41 | lineInfo, 42 | ); 43 | 44 | final GenericAnalysisError error = toGenericAnalysisError( 45 | analysisContext: analysisContext, 46 | path: path, 47 | location: location, 48 | config: config, 49 | content: content, 50 | ); 51 | errors.add(error); 52 | yield error; 53 | } 54 | } 55 | 56 | /// It doesn't work for now. 57 | /// https://github.com/dart-lang/sdk/issues/50306 58 | /// leave it in case dart team maybe support it someday in the future 59 | Stream toGenericAnalysisErrorFixesStream({ 60 | required EditGetFixesParams parameters, 61 | required AnalysisContext analysisContext, 62 | required CandiesAnalyzerPluginConfig config, 63 | }) async* { 64 | final List? errors = 65 | _cacheErrorsForFixes[parameters.file]; 66 | if (errors != null) { 67 | for (final GenericAnalysisError error in errors) { 68 | if (error.location.offset <= parameters.offset && 69 | parameters.offset <= 70 | error.location.offset + error.location.length) { 71 | yield await toGenericAnalysisErrorFixes( 72 | error: error, 73 | path: parameters.file, 74 | analysisContext: analysisContext, 75 | config: config, 76 | ); 77 | } 78 | } 79 | } 80 | } 81 | 82 | /// It doesn't work for now. 83 | /// https://github.com/dart-lang/sdk/issues/50306 84 | /// leave it in case dart team maybe support it someday in the future 85 | Future toGenericAnalysisErrorFixes({ 86 | required GenericAnalysisError error, 87 | required AnalysisContext analysisContext, 88 | required String path, 89 | required CandiesAnalyzerPluginConfig config, 90 | }) async { 91 | List fixes = await getGenericFixes( 92 | analysisContext, 93 | path, 94 | error, 95 | config, 96 | ); 97 | 98 | if (fixes.isNotEmpty) { 99 | fixes = fixes.reversed.toList(); 100 | } 101 | 102 | CandiesAnalyzerPluginLogger().log( 103 | 'get ${fixes.length} fixes for yaml lint($code) at $path', 104 | root: analysisContext.root, 105 | ); 106 | 107 | return AnalysisErrorFixes( 108 | error, 109 | fixes: [ 110 | for (int i = 0; i < fixes.length; i++) 111 | PrioritizedSourceChange(i, fixes[i]) 112 | ], 113 | ); 114 | } 115 | 116 | /// It doesn't work for now. 117 | /// https://github.com/dart-lang/sdk/issues/50306 118 | /// leave it in case dart team maybe support it someday in the future 119 | Future getGenericFix({ 120 | required AnalysisContext analysisContext, 121 | required String path, 122 | required String message, 123 | required void Function(FileEditBuilder builder) buildFileEdit, 124 | }) async { 125 | final ChangeBuilder changeBuilder = 126 | ChangeBuilder(session: analysisContext.currentSession); 127 | 128 | await changeBuilder.addGenericFileEdit( 129 | path, 130 | buildFileEdit, 131 | ); 132 | 133 | final SourceChange sourceChange = changeBuilder.sourceChange; 134 | sourceChange.message = message; 135 | return sourceChange; 136 | } 137 | 138 | /// It doesn't work for now. 139 | /// https://github.com/dart-lang/sdk/issues/50306 140 | /// leave it in case dart team maybe support it someday in the future 141 | Future> getGenericFixes( 142 | AnalysisContext analysisContext, 143 | String path, 144 | GenericAnalysisError error, 145 | CandiesAnalyzerPluginConfig config, 146 | ) async => 147 | []; 148 | 149 | GenericAnalysisError toGenericAnalysisError({ 150 | required AnalysisContext analysisContext, 151 | required String path, 152 | required Location location, 153 | required CandiesAnalyzerPluginConfig config, 154 | required String content, 155 | }) { 156 | CandiesAnalyzerPluginLogger().log( 157 | 'find error: $code at ${location.startLine} line in $path', 158 | root: analysisContext.root, 159 | ); 160 | return GenericAnalysisError( 161 | config.getSeverity(this), 162 | type, 163 | location, 164 | message, 165 | code, 166 | correction: correction, 167 | contextMessages: contextMessages, 168 | url: url, 169 | content: content, 170 | //hasFix: hasFix, 171 | ); 172 | } 173 | 174 | Location sourceSpanToLocation( 175 | String path, 176 | SourceRange sourceRange, 177 | LineInfo lineInfo, 178 | ) { 179 | final CharacterLocation startLocation = 180 | lineInfo.getLocation(sourceRange.offset); 181 | final CharacterLocation endLocation = lineInfo.getLocation(sourceRange.end); 182 | return Location( 183 | path, 184 | sourceRange.offset, 185 | sourceRange.length, 186 | startLocation.lineNumber, 187 | startLocation.columnNumber, 188 | endLine: endLocation.lineNumber, 189 | endColumn: endLocation.columnNumber, 190 | ); 191 | } 192 | 193 | final Map> _cacheErrorsForFixes = 194 | >{}; 195 | 196 | List? clearCacheErrors(String path) { 197 | return _cacheErrorsForFixes.remove(path); 198 | } 199 | 200 | List? getCacheErrors(String path) { 201 | return _cacheErrorsForFixes[path]; 202 | } 203 | 204 | Iterable getAllCacheErrors() sync* { 205 | for (final List errors 206 | in _cacheErrorsForFixes.values) { 207 | yield* errors; 208 | } 209 | } 210 | 211 | Iterable matchLint( 212 | String content, 213 | String file, 214 | LineInfo lineInfo, 215 | ); 216 | 217 | bool isFileType({ 218 | required String file, 219 | required String type, 220 | }) { 221 | return path_package.extension(file) == type; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /lib/src/error/lints/lint.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implementation_imports 2 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 3 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 4 | import 'package:candies_analyzer_plugin/src/plugin.dart'; 5 | 6 | /// The lint base 7 | abstract class CandyLint { 8 | /// The severity of the error. 9 | AnalysisErrorSeverity get severity => AnalysisErrorSeverity.INFO; 10 | 11 | /// The type of the error. 12 | AnalysisErrorType get type => AnalysisErrorType.LINT; 13 | 14 | /// The location associated with the error. 15 | //Location location; 16 | 17 | /// The message to be displayed for this error. The message should indicate 18 | /// what is wrong with the code and why it is wrong. 19 | String get message; 20 | 21 | /// The correction message to be displayed for this error. The correction 22 | /// message should indicate how the user can fix the error. The field is 23 | /// omitted if there is no correction message associated with the error code. 24 | String? get correction => null; 25 | 26 | /// The name, as a string, of the error code associated with this error. 27 | String get code; 28 | 29 | /// The URL of a page containing documentation associated with this error. 30 | String? get url => 31 | 'https://github.com/fluttercandies/candies_analyzer_plugin'; 32 | 33 | /// Additional messages associated with this diagnostic that provide context 34 | /// to help the user understand the diagnostic. 35 | List? get contextMessages => null; 36 | 37 | /// A hint to indicate to interested clients that this error has an 38 | /// associated fix (or fixes). The absence of this field implies there are 39 | /// not known to be fixes. Note that since the operation to calculate whether 40 | /// fixes apply needs to be performant it is possible that complicated tests 41 | /// will be skipped and a false negative returned. For this reason, this 42 | /// attribute should be treated as a "hint". Despite the possibility of false 43 | /// negatives, no false positives should be returned. If a client sees this 44 | /// flag set they can proceed with the confidence that there are in fact 45 | /// associated fixes. 46 | //bool? get hasFix => false; 47 | } 48 | 49 | mixin AnalyzeErrorAfterFilesAnalyzed on CandyLint { 50 | Future handleError(CandiesAnalyzerPlugin plugin, 51 | {AnalysisContext? analysisContext}); 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/error/lints/yaml_lint.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 2 | import 'package:analyzer/source/line_info.dart'; 3 | import 'package:analyzer/source/source_range.dart'; 4 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 5 | import 'package:analyzer_plugin/protocol/protocol_generated.dart'; 6 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; 7 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_yaml.dart'; 8 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 9 | 10 | /// The yaml lint base 11 | abstract class YamlLint extends CandyLint { 12 | Iterable toYamlAnalysisErrors({ 13 | required AnalysisContext analysisContext, 14 | required String path, 15 | required CandiesAnalyzerPluginConfig config, 16 | required YamlNode root, 17 | required String content, 18 | required LineInfo lineInfo, 19 | }) sync* { 20 | final List nodes = matchLint( 21 | root, 22 | content, 23 | lineInfo, 24 | ).toList(); 25 | 26 | CandiesAnalyzerPluginLogger().log( 27 | 'find ${nodes.length} yaml lint($code) at $path', 28 | root: analysisContext.root, 29 | ); 30 | 31 | final List errors = []; 32 | _cacheErrorsForFixes[path] = errors; 33 | for (final SourceRange node in nodes) { 34 | final Location location = sourceSpanToLocation( 35 | path, 36 | node, 37 | lineInfo, 38 | ); 39 | 40 | final YamlAnalysisError error = toYamlAnalysisError( 41 | analysisContext: analysisContext, 42 | path: path, 43 | location: location, 44 | config: config, 45 | root: root, 46 | content: content, 47 | ); 48 | errors.add(error); 49 | yield error; 50 | } 51 | } 52 | 53 | /// It doesn't work for now. 54 | /// https://github.com/dart-lang/sdk/issues/50306 55 | /// leave it in case dart team maybe support it someday in the future 56 | Stream toYamlAnalysisErrorFixesStream({ 57 | required EditGetFixesParams parameters, 58 | required AnalysisContext analysisContext, 59 | required CandiesAnalyzerPluginConfig config, 60 | }) async* { 61 | final List? errors = 62 | _cacheErrorsForFixes[parameters.file]; 63 | if (errors != null) { 64 | for (final YamlAnalysisError error in errors) { 65 | if (error.location.offset <= parameters.offset && 66 | parameters.offset <= 67 | error.location.offset + error.location.length) { 68 | yield await toYamlAnalysisErrorFixes( 69 | error: error, 70 | path: parameters.file, 71 | analysisContext: analysisContext, 72 | config: config, 73 | ); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// It doesn't work for now. 80 | /// https://github.com/dart-lang/sdk/issues/50306 81 | /// leave it in case dart team maybe support it someday in the future 82 | Future toYamlAnalysisErrorFixes({ 83 | required YamlAnalysisError error, 84 | required AnalysisContext analysisContext, 85 | required String path, 86 | required CandiesAnalyzerPluginConfig config, 87 | }) async { 88 | List fixes = await getYamlFixes( 89 | analysisContext, 90 | path, 91 | error, 92 | config, 93 | ); 94 | 95 | if (fixes.isNotEmpty) { 96 | fixes = fixes.reversed.toList(); 97 | } 98 | 99 | CandiesAnalyzerPluginLogger().log( 100 | 'get ${fixes.length} fixes for yaml lint($code) at $path', 101 | root: analysisContext.root, 102 | ); 103 | 104 | return AnalysisErrorFixes( 105 | error, 106 | fixes: [ 107 | for (int i = 0; i < fixes.length; i++) 108 | PrioritizedSourceChange(i, fixes[i]) 109 | ], 110 | ); 111 | } 112 | 113 | /// It doesn't work for now. 114 | /// https://github.com/dart-lang/sdk/issues/50306 115 | /// leave it in case dart team maybe support it someday in the future 116 | Future getYamlFix({ 117 | required AnalysisContext analysisContext, 118 | required String path, 119 | required String message, 120 | required void Function(YamlFileEditBuilder builder) buildYamlFileEdit, 121 | }) async { 122 | final ChangeBuilder changeBuilder = 123 | ChangeBuilder(session: analysisContext.currentSession); 124 | 125 | await changeBuilder.addYamlFileEdit( 126 | path, 127 | buildYamlFileEdit, 128 | ); 129 | 130 | final SourceChange sourceChange = changeBuilder.sourceChange; 131 | sourceChange.message = message; 132 | return sourceChange; 133 | } 134 | 135 | /// It doesn't work for now. 136 | /// https://github.com/dart-lang/sdk/issues/50306 137 | /// leave it in case dart team maybe support it someday in the future 138 | Future> getYamlFixes( 139 | AnalysisContext analysisContext, 140 | String path, 141 | YamlAnalysisError error, 142 | CandiesAnalyzerPluginConfig config, 143 | ) async => 144 | []; 145 | 146 | YamlAnalysisError toYamlAnalysisError({ 147 | required AnalysisContext analysisContext, 148 | required String path, 149 | required Location location, 150 | required CandiesAnalyzerPluginConfig? config, 151 | required YamlNode root, 152 | required String content, 153 | }) { 154 | CandiesAnalyzerPluginLogger().log( 155 | 'find error: $code at ${location.startLine} line in $path', 156 | root: analysisContext.root, 157 | ); 158 | return YamlAnalysisError( 159 | config?.getSeverity(this) ?? severity, 160 | type, 161 | location, 162 | message, 163 | code, 164 | correction: correction, 165 | contextMessages: contextMessages, 166 | url: url, 167 | content: content, 168 | root: root, 169 | //hasFix: hasFix, 170 | ); 171 | } 172 | 173 | Location sourceSpanToLocation( 174 | String path, 175 | SourceRange sourceRange, 176 | LineInfo lineInfo, 177 | ) { 178 | final CharacterLocation startLocation = 179 | lineInfo.getLocation(sourceRange.offset); 180 | final CharacterLocation endLocation = lineInfo.getLocation(sourceRange.end); 181 | return Location( 182 | path, 183 | sourceRange.offset, 184 | sourceRange.length, 185 | startLocation.lineNumber, 186 | startLocation.columnNumber, 187 | endLine: endLocation.lineNumber, 188 | endColumn: endLocation.columnNumber, 189 | ); 190 | } 191 | 192 | final Map> _cacheErrorsForFixes = 193 | >{}; 194 | 195 | List? clearCacheErrors(String path) { 196 | return _cacheErrorsForFixes.remove(path); 197 | } 198 | 199 | List? getCacheErrors(String path) { 200 | return _cacheErrorsForFixes[path]; 201 | } 202 | 203 | Iterable getAllCacheErrors() sync* { 204 | for (final List errors in _cacheErrorsForFixes.values) { 205 | yield* errors; 206 | } 207 | } 208 | 209 | Iterable matchLint( 210 | YamlNode root, 211 | String content, 212 | LineInfo lineInfo, 213 | ); 214 | } 215 | -------------------------------------------------------------------------------- /lib/src/error/plugin/dart_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 2 | import 'package:analyzer/dart/analysis/results.dart'; 3 | import 'package:analyzer_plugin/plugin/plugin.dart'; 4 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 5 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 6 | 7 | mixin CandiesDartFileErrorPlugin on ServerPlugin { 8 | /// AstVisitor to check lint 9 | AstVisitorBase get astVisitor => CandiesLintsAstVisitor(); 10 | 11 | /// The dart lints to be used to analyze dart files 12 | List get dartLints => [ 13 | PreferAssetConst(), 14 | PreferNamedRoutes(), 15 | PerferSafeSetState(), 16 | MustCallSuperDispose(), 17 | EndCallSuperDispose(), 18 | PerferDocComments(), 19 | PreferSingleton(), 20 | //UnusedFile(), 21 | GoodDocComments(), 22 | PreferTrailingComma(), 23 | ]; 24 | 25 | Future> analyzeDartFile({ 26 | required AnalysisContext analysisContext, 27 | required String path, 28 | required CandiesAnalyzerPluginConfig config, 29 | }) async { 30 | final SomeResolvedUnitResult unitResult = 31 | await analysisContext.currentSession.getResolvedUnit(path); 32 | if (unitResult is ResolvedUnitResult) { 33 | return config.getDartErrorsFromResult(result: unitResult); 34 | } 35 | CandiesAnalyzerPluginLogger().logError('getResolvedUnit failed for $path', 36 | root: analysisContext.root); 37 | return []; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/error/plugin/generic_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 4 | import 'package:analyzer/source/line_info.dart'; 5 | import 'package:analyzer_plugin/plugin/plugin.dart'; 6 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 7 | import 'package:candies_analyzer_plugin/src/config.dart'; 8 | import 'package:candies_analyzer_plugin/src/error/lints/generic_lint.dart'; 9 | 10 | mixin CandiesGenericFileErrorPlugin on ServerPlugin { 11 | /// The generic lints to be used to analyze generic files 12 | List get genericLints => []; 13 | 14 | Iterable analyzeGenericFile({ 15 | required AnalysisContext analysisContext, 16 | required String path, 17 | required CandiesAnalyzerPluginConfig config, 18 | }) sync* { 19 | final String content = File(path).readAsStringSync(); 20 | final LineInfo lineInfo = LineInfo.fromContent(content); 21 | for (final GenericLint lint in config.genericLints) { 22 | yield* lint.toGenericAnalysisErrors( 23 | analysisContext: analysisContext, 24 | path: path, 25 | config: config, 26 | content: content, 27 | lineInfo: lineInfo, 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/error/plugin/yaml_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 4 | import 'package:analyzer/source/line_info.dart'; 5 | import 'package:analyzer_plugin/plugin/plugin.dart'; 6 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 7 | import 'package:candies_analyzer_plugin/src/config.dart'; 8 | import 'package:candies_analyzer_plugin/src/error/lints/yaml_lint.dart'; 9 | import 'package:yaml/yaml.dart'; 10 | 11 | mixin CandiesYamlFileErrorPlugin on ServerPlugin { 12 | /// The yaml lints to be used to analyze yaml files 13 | List get yamlLints => []; 14 | 15 | Iterable analyzeYamlFile({ 16 | required AnalysisContext analysisContext, 17 | required String path, 18 | required CandiesAnalyzerPluginConfig config, 19 | }) sync* { 20 | final String content = File(path).readAsStringSync(); 21 | final YamlNode root = loadYamlNode(content); 22 | //final String baseName = path_package.basename(path); 23 | // Pubspec? pubspec; 24 | // if (baseName == 'pubspec.yaml') { 25 | // pubspec = Pubspec.parse(content); 26 | // } 27 | final LineInfo lineInfo = LineInfo.fromContent(content); 28 | for (final YamlLint lint in config.yamlLints) { 29 | yield* lint.toYamlAnalysisErrors( 30 | analysisContext: analysisContext, 31 | path: path, 32 | config: config, 33 | root: root, 34 | content: content, 35 | lineInfo: lineInfo, 36 | ); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/extension.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 4 | import 'package:analyzer/dart/analysis/results.dart'; 5 | import 'package:analyzer/dart/ast/ast.dart'; 6 | import 'package:analyzer/dart/ast/syntactic_entity.dart'; 7 | import 'package:analyzer/dart/element/element.dart'; 8 | import 'package:analyzer/error/error.dart' as error; 9 | import 'package:analyzer/source/error_processor.dart'; 10 | import 'package:analyzer/source/line_info.dart'; 11 | import 'package:analyzer/source/source_range.dart'; 12 | import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; 13 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 14 | import 'package:candies_analyzer_plugin/src/error/lints/lint.dart'; 15 | import 'package:path/path.dart' as path; 16 | 17 | import 'ansi_code.dart'; 18 | 19 | /// The extension for ResolvedUnitResult 20 | extension ResolvedUnitResultE on ResolvedUnitResult { 21 | /// Return root path 22 | String get root => session.analysisContext.contextRoot.root.path; 23 | 24 | /// Return line base on offset 25 | int lineNumber(int offset) => lineInfo.getLocation(offset).lineNumber; 26 | } 27 | 28 | /// The extension for AnalysisContext 29 | extension AnalysisContextE on AnalysisContext { 30 | /// Return root path 31 | String get root => contextRoot.root.path; 32 | 33 | Future get getSdkRoot async { 34 | if (this.sdkRoot != null) { 35 | return this.sdkRoot!.path; 36 | } 37 | final SomeLibraryElementResult coreSource = 38 | await currentSession.getLibraryByUri('dart:core'); 39 | late String sdkRoot; 40 | final path.Context pathContext = 41 | currentSession.resourceProvider.pathContext; 42 | if (coreSource is LibraryElementResult) { 43 | // "flutter/3.0.0/bin/cache/pkg/sky_engine/lib/core/core.dart" 44 | sdkRoot = coreSource.element.source.fullName; 45 | 46 | while (pathContext.basename(sdkRoot) != 'lib') { 47 | final String parent = pathContext.dirname(sdkRoot); 48 | if (parent == sdkRoot) { 49 | break; 50 | } 51 | sdkRoot = parent; 52 | } 53 | } else { 54 | // flutter/3.0.0/bin/cache/dart-sdk 55 | sdkRoot = path.dirname(path.dirname(Platform.resolvedExecutable)); 56 | } 57 | return sdkRoot; 58 | } 59 | 60 | Future get flutterSdkRoot async { 61 | final String sdk = await getSdkRoot; 62 | final path.Context pathContext = 63 | currentSession.resourceProvider.pathContext; 64 | final String tag = pathContext.join( 65 | 'bin', 66 | 'cache', 67 | ); 68 | // flutter/3.0.0/ 69 | // dart sdk in flutter 70 | if (sdk.contains(tag)) { 71 | // 72 | return sdk.substring( 73 | 0, 74 | sdk.indexOf(tag), 75 | ); 76 | } 77 | 78 | return null; 79 | } 80 | } 81 | 82 | /// The extension for DartFileEditBuilder 83 | extension DartFileEditBuilderE on DartFileEditBuilder { 84 | /// Format all content 85 | void formatAll(CompilationUnit unit) => format(SourceRange(0, unit.end)); 86 | } 87 | 88 | extension ErrorProcessorE on ErrorProcessor { 89 | /// If severity is `null`, this processor will "filter" the associated error code. 90 | bool get filtered => severity == null; 91 | 92 | bool ignore(CandyLint lint) { 93 | return filtered && same(lint); 94 | } 95 | 96 | AnalysisErrorSeverity getSeverity(CandyLint lint) { 97 | if (same(lint) && !filtered) { 98 | switch (severity) { 99 | case error.ErrorSeverity.INFO: 100 | return AnalysisErrorSeverity.INFO; 101 | case error.ErrorSeverity.WARNING: 102 | return AnalysisErrorSeverity.WARNING; 103 | case error.ErrorSeverity.ERROR: 104 | return AnalysisErrorSeverity.ERROR; 105 | default: 106 | } 107 | } 108 | return lint.severity; 109 | } 110 | 111 | bool same(CandyLint lint) => lint.code.toUpperCase() == code; 112 | } 113 | 114 | extension LibraryElementE on LibraryElement { 115 | bool get isInFlutterSdk { 116 | final Uri uri = definingCompilationUnit.source.uri; 117 | if (uri.scheme == 'package') { 118 | if (uri.pathSegments.isNotEmpty) { 119 | return uri.pathSegments.first == 'flutter'; 120 | } 121 | } 122 | return false; 123 | } 124 | } 125 | 126 | extension ElementE on Element { 127 | bool get isInFlutterSdk { 128 | if (library == null) { 129 | return false; 130 | } 131 | return library!.isInFlutterSdk; 132 | } 133 | 134 | bool get isInSdk { 135 | if (library == null) { 136 | return false; 137 | } 138 | return library!.isInSdk; 139 | } 140 | } 141 | 142 | extension SyntacticEntityE on SyntacticEntity { 143 | int startLineNumber(LineInfo lineInfo) => 144 | lineInfo.getLocation(offset).lineNumber; 145 | 146 | int endLineNumber(LineInfo lineInfo) => lineInfo.getLocation(end).lineNumber; 147 | } 148 | 149 | extension AnalysisErrorE on AnalysisError { 150 | String toConsoleInfo(String root) { 151 | final String link = 152 | '${path.relative(location.file, from: root)}:${location.startLine}:${location.startColumn}'; 153 | return [ 154 | severity.name.toLowerCase(), 155 | link, 156 | message, 157 | code, 158 | ].join(ErrorInfoE.separator); 159 | } 160 | } 161 | 162 | extension ErrorInfoE on String { 163 | static const String separator = ' - '; 164 | String getHighlightErrorInfo() { 165 | // info - bin/pre_commit.dart:17:10 - The value of the local variable 'info' isn't used. Try removing the variable or using it. - unused_local_variable 166 | 167 | final List infos = 168 | trim().split(separator).map((String e) => e.trim()).toList(); 169 | if (infos.length == 4) { 170 | infos[1] = infos[1].wrapAnsiCode( 171 | foregroundColor: AnsiCodeForegroundColor.blue, 172 | style: AnsiCodeStyle.underlined, 173 | ); 174 | infos[3] = 175 | infos[3].wrapAnsiCode(foregroundColor: AnsiCodeForegroundColor.green); 176 | 177 | String severity = infos[0]; 178 | switch (severity) { 179 | case 'error': 180 | severity = severity.wrapAnsiCode( 181 | foregroundColor: AnsiCodeForegroundColor.red); 182 | break; 183 | case 'warning': 184 | severity = severity.wrapAnsiCode( 185 | foregroundColor: AnsiCodeForegroundColor.yellow); 186 | break; 187 | case 'info': 188 | break; 189 | default: 190 | } 191 | infos[0] = severity; 192 | 193 | return infos.join(separator); 194 | } 195 | 196 | return this; 197 | } 198 | 199 | List getErrorsFromDartAnalyze() { 200 | // Analyzing analyzer_plugin... 201 | 202 | // info - bin/pre_commit.dart:17:10 - The value of the local variable 'info' isn't used. Try removing the variable or using it. - unused_local_variable 203 | 204 | // 1 issue found. 205 | 206 | if (!contains('No issues found')) { 207 | final List lines = split('\n'); 208 | lines.removeWhere((String element) => element.trim().isEmpty); 209 | if (lines.length > 2) { 210 | lines.removeLast(); 211 | lines.removeAt(0); 212 | } 213 | 214 | return lines.map((String e) => e.trim()).toList(); 215 | } 216 | return []; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/src/ignore_info.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implementation_imports, unused_import 2 | 3 | import 'package:analyzer/dart/analysis/results.dart'; 4 | import 'package:analyzer/source/line_info.dart'; 5 | import 'package:analyzer/source/source_range.dart'; 6 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 7 | import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; 8 | import 'package:analyzer/src/ignore_comments/ignore_info.dart'; 9 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 10 | 11 | /// The class to help ignore error 12 | class CandiesAnalyzerPluginIgnoreInfo { 13 | CandiesAnalyzerPluginIgnoreInfo.forDart(this.result) { 14 | final String content = result.content; 15 | final LineInfo lineInfo = result.lineInfo; 16 | //final IgnoreInfo info = IgnoreInfo.forDart(result.unit, result.content); 17 | for (final RegExpMatch match in _ignoreMatchers.allMatches(content)) { 18 | // store for quick fix 19 | _ignoreForThisLIneMatches.add(match); 20 | final List codes = 21 | match.group(1)!.split(',').map(_toLowerCase).toList(); 22 | // remove empty code 23 | codes.remove(''); 24 | final CharacterLocation location = lineInfo.getLocation(match.start); 25 | // IgnoreInfo from analyzer 26 | // The comment is on its own line, so it refers to the next line. 27 | // TODO(zmtzawqlp): don't understand about this. 28 | 29 | // This is line number in ide 30 | final int lineNumber = location.lineNumber; 31 | _ignoreForThisLineMap 32 | .putIfAbsent( 33 | lineNumber, 34 | () => [], 35 | ) 36 | .addAll(codes); 37 | } 38 | 39 | for (final RegExpMatch match in _ignoreForFileMatcher.allMatches(content)) { 40 | // store for quick fix 41 | _ignoreForThisFileMatches.add(match); 42 | _ignoreForFileSet.addAll(match.group(1)!.split(',').map(_toLowerCase)); 43 | // remove empty code 44 | _ignoreForFileSet.remove(''); 45 | } 46 | } 47 | 48 | final Map> _ignoreForThisLineMap = >{}; 49 | final Set _ignoreForFileSet = {}; 50 | final List _ignoreForThisLIneMatches = []; 51 | final List _ignoreForThisFileMatches = []; 52 | final ResolvedUnitResult result; 53 | 54 | static final RegExp _ignoreMatchers = 55 | //IgnoreInfo.IGNORE_MATCHER; 56 | RegExp('//[ ]*ignore:(.*)', multiLine: true); 57 | 58 | static final RegExp _ignoreForFileMatcher = 59 | // IgnoreInfo.IGNORE_FOR_FILE_MATCHER; 60 | RegExp('//[ ]*ignore_for_file:(.*)', multiLine: true); 61 | 62 | /// Return `true` if the [code] is ignored at the file. 63 | bool ignored(String code) => _ignoreForFileSet.contains(_toLowerCase(code)); 64 | 65 | /// Return `true` if the [code] is ignored at the given [line]. 66 | bool ignoredAt(String code, int line) => 67 | ignored(code) || 68 | (_ignoreForThisLineMap[line - 1]?.contains(_toLowerCase(code)) ?? false); 69 | 70 | String _toLowerCase(String code) => code.trim().toLowerCase(); 71 | 72 | /// The builder of ignore for this file. 73 | void fixIgnoreForThisFile( 74 | String code, { 75 | DartFileEditBuilder? dartFileEditBuilder, 76 | bool formatAll = true, 77 | }) { 78 | if (_ignoreForThisFileMatches.isEmpty) { 79 | dartFileEditBuilder?.addSimpleInsertion( 80 | 0, '\/\/ ignore_for_file: $code\n'); 81 | } else { 82 | for (final RegExpMatch match in _ignoreForThisFileMatches) { 83 | dartFileEditBuilder?.addSimpleInsertion( 84 | match.end, '${_ignoreForFileSet.isEmpty ? '' : ','} $code'); 85 | } 86 | } 87 | 88 | if (formatAll) { 89 | dartFileEditBuilder?.formatAll(result.unit); 90 | } 91 | } 92 | 93 | /// The builder of ignore for this line. 94 | void fixIgnoreForThisLine( 95 | String code, 96 | Location location, { 97 | DartFileEditBuilder? dartFileEditBuilder, 98 | bool formatAll = true, 99 | }) { 100 | // ide line number 101 | final int ideLineNumber = location.startLine; 102 | // add after ignore: 103 | // previous line has ignore 104 | if (_ignoreForThisLineMap.containsKey(ideLineNumber - 1)) { 105 | final List codes = 106 | _ignoreForThisLineMap[ideLineNumber - 1] ?? []; 107 | for (final RegExpMatch match in _ignoreForThisLIneMatches) { 108 | // ide number 109 | final int line = result.lineInfo.getLocation(match.start).lineNumber; 110 | if (line == ideLineNumber - 1) { 111 | dartFileEditBuilder?.addSimpleInsertion( 112 | match.end, '${codes.isEmpty ? '' : ','} $code'); 113 | } 114 | } 115 | } 116 | // add previous line 117 | else { 118 | // getOffsetOfLine line number should be index number 119 | // 120 | // current line 121 | // ideLineNumber 122 | // index is begin with 0 123 | // ide is begin with 1 124 | // getOffsetOfLine should use index number 125 | final int indexLine = ideLineNumber - 1; 126 | final int firstChartOffset = result.lineInfo.getOffsetOfLine(indexLine); 127 | 128 | final int columnNumber = 129 | result.lineInfo.getLocation(firstChartOffset).columnNumber; 130 | String space = ''; 131 | for (int i = firstChartOffset; i < location.offset; i++) { 132 | final String char = result.content[i]; 133 | if (char.trim() != '') { 134 | break; 135 | } 136 | space += char; 137 | } 138 | 139 | final int lineStartOffset = firstChartOffset - columnNumber; 140 | final String fix = '\/\/ ignore: $code'; 141 | dartFileEditBuilder?.addSimpleInsertion( 142 | lineStartOffset, 143 | '\n$space$fix', 144 | ); 145 | } 146 | if (formatAll) { 147 | dartFileEditBuilder?.formatAll(result.unit); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /lib/src/log.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | // ignore: implementation_imports 3 | import 'package:logger/src/outputs/file_output.dart'; 4 | import 'dart:io' as io; 5 | import 'package:path/path.dart' as path; 6 | 7 | /// The logger for this plugin 8 | class CandiesAnalyzerPluginLogger { 9 | factory CandiesAnalyzerPluginLogger() => _candiesAnalyzerPluginLogger; 10 | CandiesAnalyzerPluginLogger._(); 11 | static final CandiesAnalyzerPluginLogger _candiesAnalyzerPluginLogger = 12 | CandiesAnalyzerPluginLogger._(); 13 | final Map _loggers = {}; 14 | 15 | /// The name of log file 16 | String logFileName = 'candies_analyzer_plugin'; 17 | 18 | /// whether should log 19 | bool shouldLog = false; 20 | void _init(String root) { 21 | if (!_loggers.containsKey(root)) { 22 | _loggers[root] = Logger( 23 | filter: _Filter(), 24 | printer: PrettyPrinter( 25 | methodCount: 0, 26 | printTime: true, 27 | ), 28 | output: FileOutput( 29 | file: io.File(path.join( 30 | root, 31 | '$logFileName.log', 32 | )), 33 | overrideExisting: true, 34 | )); 35 | 36 | log('analyze at : $root', root: root); 37 | } 38 | } 39 | 40 | /// Log info 41 | void log( 42 | dynamic message, { 43 | required String root, 44 | dynamic error, 45 | StackTrace? stackTrace, 46 | bool forceLog = false, 47 | }) { 48 | if (!shouldLog && !forceLog) { 49 | return; 50 | } 51 | _init(root); 52 | _loggers[root]?.d(message, error, stackTrace); 53 | } 54 | 55 | /// Log error 56 | void logError( 57 | dynamic message, { 58 | required String root, 59 | dynamic error, 60 | StackTrace? stackTrace, 61 | bool forceLog = false, 62 | }) { 63 | if (!shouldLog && !forceLog) { 64 | return; 65 | } 66 | _init(root); 67 | _loggers[root]?.e(message, error, stackTrace); 68 | } 69 | } 70 | 71 | class _Filter extends LogFilter { 72 | @override 73 | bool shouldLog(LogEvent event) { 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/plugin_base.dart: -------------------------------------------------------------------------------- 1 | part of 'plugin.dart'; 2 | 3 | /// Put properties or methods not from [ServerPlugin] here. 4 | mixin CandiesAnalyzerPluginBase on ServerPlugin { 5 | /// cache errors into file 6 | bool get cacheErrorsIntoFile => false; 7 | static const String cacheErrorsFileName = '.candies_cache_error.log'; 8 | 9 | /// Add git author into error message 10 | bool get showAnalysisErrorWithGitAuthor => false; 11 | 12 | /// The name of log file 13 | /// default this.name 14 | String get logFileName => name; 15 | 16 | /// The cache of configs 17 | final Map _configs = 18 | {}; 19 | Map get configs => _configs; 20 | List? __fileGlobsToAnalyze; 21 | 22 | List get _fileGlobsToAnalyze => 23 | __fileGlobsToAnalyze ??= fileGlobsToAnalyze 24 | .map((String e) => Glob(path_package.separator, e)) 25 | .toList(); 26 | 27 | /// whether should analyze this file 28 | bool shouldAnalyzeFile( 29 | String file, 30 | AnalysisContext analysisContext, 31 | ) { 32 | if (file.endsWith('.g.dart')) { 33 | return false; 34 | } 35 | 36 | final String relative = path_package.relative( 37 | file, 38 | from: analysisContext.root, 39 | ); 40 | 41 | for (final Glob pattern in _fileGlobsToAnalyze) { 42 | if (pattern.matches(relative)) { 43 | return true; 44 | } 45 | } 46 | return false; 47 | } 48 | 49 | /// get analysis error fixes form cache errors 50 | Stream getAnalysisErrorFixes( 51 | CandiesAnalyzerPluginConfig config, 52 | EditGetFixesParams parameters, 53 | AnalysisContext context, 54 | ) { 55 | return config.getAnalysisErrorFixes( 56 | parameters: parameters, 57 | analysisContext: context, 58 | ); 59 | } 60 | 61 | /// Get git author form error line 62 | String? getGitAuthor( 63 | String fileName, 64 | int line, 65 | ) { 66 | final ProcessResult result = Process.runSync( 67 | 'git', 68 | 'blame ${path_package.basename(fileName)} -L$line,$line'.split(' '), 69 | runInShell: true, 70 | workingDirectory: path_package.dirname(fileName), 71 | ); 72 | 73 | // ^bb984a5 (zmtzawqlp 2022-10-22 22:24:19 +0800 1) // ignore_for_file: unused_local_variable 74 | if (result.exitCode != 0) { 75 | return null; 76 | } 77 | final String stdout = '${result.stdout}'; 78 | if (stdout.startsWith('fatal')) { 79 | return null; 80 | } 81 | 82 | final int start = stdout.indexOf('('); 83 | if (start > -1) { 84 | final int end = stdout.indexOf(')', start); 85 | if (end > -1) { 86 | final List infos = 87 | stdout.substring(start + 1, end).trim().split(' '); 88 | if (infos.isNotEmpty) { 89 | return infos.first; 90 | } 91 | } 92 | } 93 | return null; 94 | } 95 | 96 | /// before send AnalysisErrors Notification 97 | /// you can edit AnalysisError before to be send 98 | Future beforeSendAnalysisErrors({ 99 | required List errors, 100 | required AnalysisContext analysisContext, 101 | required String path, 102 | required CandiesAnalyzerPluginConfig config, 103 | }) async { 104 | if (errors.isNotEmpty && showAnalysisErrorWithGitAuthor) { 105 | for (final AnalysisError error in errors) { 106 | final String? author = getGitAuthor(path, error.location.startLine); 107 | if (author != null) { 108 | error.message = '($author) ' + error.message; 109 | } else { 110 | // fatal: not a git repository (or any of the parent directories): .git 111 | // or has error when run git blame 112 | break; 113 | } 114 | } 115 | } 116 | } 117 | 118 | Map> groupBy(Iterable values, T Function(S) key) { 119 | final Map> map = >{}; 120 | for (final S element in values) { 121 | (map[key(element)] ??= []).add(element); 122 | } 123 | return map; 124 | } 125 | 126 | Iterable getAllCacheErrors({String? code}) sync* { 127 | for (final CandiesAnalyzerPluginConfig config in _configs.values) { 128 | yield* config.getAllCacheErrors(code: code); 129 | } 130 | } 131 | 132 | Iterable getCacheErrors(String path, {String? code}) sync* { 133 | for (final CandiesAnalyzerPluginConfig config in _configs.values) { 134 | yield* config.getCacheErrors(path, code: code); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/src/plugin_starter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:isolate'; 2 | import 'package:analyzer_plugin/starter.dart'; 3 | import 'package:candies_analyzer_plugin/src/plugin.dart'; 4 | 5 | /// An object that can be used to start an analysis server plugin. This class 6 | /// exists so that clients can configure a plugin before starting it. 7 | class CandiesAnalyzerPluginStarter { 8 | CandiesAnalyzerPluginStarter._(); 9 | 10 | /// Establish the channel used to communicate with the server and start the 11 | /// plugin. 12 | static void start( 13 | List args, 14 | SendPort sendPort, { 15 | CandiesAnalyzerPlugin? plugin, 16 | }) { 17 | ServerPluginStarter( 18 | plugin ?? CandiesAnalyzerPlugin(), 19 | ).start(sendPort); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: candies_analyzer_plugin 2 | description: The plugin to help create custom analyzer plugin quickly and provide some useful lints and get suggestion and auto import for extension member. 3 | version: 5.0.2 4 | homepage: https://github.com/fluttercandies/candies_analyzer_plugin 5 | 6 | environment: 7 | sdk: '>=2.17.6 <3.0.0' 8 | 9 | dependencies: 10 | path: ^1.8.0 11 | analyzer: ^4.7.0 12 | analyzer_plugin: ^0.11.1 13 | logger: ^1.1.0 14 | yaml: ^3.1.1 15 | pubspec_parse: ^1.2.1 16 | io: ^1.0.3 17 | args: ^2.3.1 18 | dev_dependencies: 19 | lints: ^2.0.0 20 | test: ^1.21.4 21 | 22 | executables: 23 | candies_analyzer_plugin: main 24 | 25 | # dart pub global activate --source path ./ 26 | # candies_analyzer_plugin clear_cache -------------------------------------------------------------------------------- /test/candies_analyzer_plugin_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_import, unused_local_variable 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 6 | import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; 7 | import 'package:analyzer/dart/analysis/analysis_options.dart'; 8 | import 'package:analyzer/dart/analysis/results.dart'; 9 | import 'package:analyzer/dart/analysis/utilities.dart'; 10 | import 'package:analyzer/source/line_info.dart'; 11 | import 'package:analyzer_plugin/protocol/protocol_common.dart'; 12 | import 'package:analyzer/src/ignore_comments/ignore_info.dart'; 13 | import 'package:analyzer_plugin/protocol/protocol_generated.dart'; 14 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 15 | import 'package:analyzer/src/util/glob.dart'; 16 | import 'package:test/test.dart'; 17 | import 'package:path/path.dart' as path; 18 | 19 | Future main() async { 20 | final String debugFilePath = 21 | path.join(path.current, 'example', 'lib', 'main.dart'); 22 | test('getErrors', () async { 23 | final String debugFilePath = Directory.current.parent.parent.parent.path; 24 | final AnalysisContextCollection collection = 25 | AnalysisContextCollection(includedPaths: [debugFilePath]); 26 | 27 | final CandiesAnalyzerPlugin myPlugin = CandiesAnalyzerPlugin(); 28 | for (final AnalysisContext context in collection.contexts) { 29 | for (final String file in context.contextRoot.analyzedFiles()) { 30 | if (!myPlugin.shouldAnalyzeFile(file, context)) { 31 | continue; 32 | } 33 | final List errors = 34 | (await myPlugin.getAnalysisErrorsForDebug( 35 | file, 36 | context, 37 | )) 38 | .toList(); 39 | for (final AnalysisError error in errors) { 40 | final List fixes = await myPlugin 41 | .getAnalysisErrorFixesForDebug( 42 | EditGetFixesParams(file, error.location.offset), context) 43 | .toList(); 44 | print(fixes.length); 45 | } 46 | 47 | print(errors.length); 48 | } 49 | } 50 | //LineInfo lineInfo = LineInfo.fromContent(result.content); 51 | }); 52 | 53 | test('ignore for this line', () async { 54 | final ResolvedUnitResult result = 55 | await resolveFile2(path: debugFilePath) as ResolvedUnitResult; 56 | 57 | final IgnoreInfo info = IgnoreInfo.forDart(result.unit, result.content); 58 | // info.ignoredAt(ErrorCode(), line) 59 | 60 | final LineInfo lineInfo = LineInfo.fromContent(result.content); 61 | final int offset = 62 | result.content.indexOf('onDoubleTap: () => setState(() {})'); 63 | final CharacterLocation characterLocation = lineInfo.getLocation(offset); 64 | final CandiesAnalyzerPluginIgnoreInfo ignore = 65 | CandiesAnalyzerPluginIgnoreInfo.forDart(result); 66 | ignore.fixIgnoreForThisLine( 67 | 'ddd', 68 | Location( 69 | '', 70 | offset, 71 | 1, 72 | characterLocation.lineNumber, 73 | characterLocation.columnNumber, 74 | )); 75 | }); 76 | 77 | test('ignore for this line (with ignore)', () async { 78 | final ResolvedUnitResult result = 79 | await resolveFile2(path: debugFilePath) as ResolvedUnitResult; 80 | final LineInfo lineInfo = LineInfo.fromContent(result.content); 81 | final int offset = result.content.indexOf('onTap: () => setState(() {})'); 82 | final CharacterLocation characterLocation = lineInfo.getLocation(offset); 83 | final CandiesAnalyzerPluginIgnoreInfo ignore = 84 | CandiesAnalyzerPluginIgnoreInfo.forDart(result); 85 | ignore.fixIgnoreForThisLine( 86 | 'ddd', 87 | Location( 88 | '', 89 | offset, 90 | 1, 91 | characterLocation.lineNumber, 92 | characterLocation.columnNumber, 93 | )); 94 | }); 95 | 96 | test('AnalysisContextCollection', () async { 97 | final AnalysisContextCollection collection = AnalysisContextCollection( 98 | includedPaths: [path.join(path.current, 'example')]); 99 | for (final AnalysisContext context in collection.contexts) { 100 | final AnalysisOptions sss = context.analysisOptions; 101 | 102 | final String? optionsFilePath = context.contextRoot.optionsFile?.path; 103 | if (optionsFilePath != null) { 104 | final File file = File(optionsFilePath); 105 | final YamlMap yaml = loadYaml(file.readAsStringSync()) as YamlMap; 106 | 107 | if (yaml.containsKey('include')) { 108 | final YamlScalar s = yaml.nodes['include'] as YamlScalar; 109 | print('dd'); 110 | } 111 | if (yaml.containsKey('linter')) { 112 | final YamlMap s = yaml.nodes['linter'] as YamlMap; 113 | if (s.containsKey('rules')) { 114 | if (s.nodes['rules'] is YamlList) { 115 | } else if (s.nodes['rules'] is YamlMap) {} 116 | final YamlNode? ss = s.nodes['rules']; 117 | 118 | print('ddd'); 119 | } 120 | print('dd'); 121 | } 122 | if (yaml.nodes['custom_lint'] is YamlMap) { 123 | final YamlMap pluginConfig = yaml.nodes['custom_lint'] as YamlMap; 124 | if (pluginConfig.nodes['include'] is YamlList) { 125 | final YamlList includePatterns = 126 | pluginConfig.nodes['include'] as YamlList; 127 | 128 | final List _includePatterns = includePatterns 129 | .map((dynamic e) => Glob(path.separator, e.toString())) 130 | .toList(); 131 | 132 | bool include(String path) { 133 | if (_includePatterns.isEmpty) { 134 | return true; 135 | } 136 | 137 | for (final Glob includePattern in _includePatterns) { 138 | if (includePattern.matches(path)) { 139 | return true; 140 | } 141 | } 142 | return false; 143 | } 144 | 145 | final String testFile = path.join( 146 | path.current, 'example', 'lib', 'include', 'test.dart'); 147 | 148 | final String relative = 149 | path.relative(testFile, from: context.contextRoot.root.path); 150 | 151 | final bool ddd = include(relative); 152 | 153 | print('ddd'); 154 | } 155 | } 156 | print('dd'); 157 | } 158 | 159 | print('dd'); 160 | } 161 | }); 162 | } 163 | -------------------------------------------------------------------------------- /tools/analyzer_plugin/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /tools/analyzer_plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version. 4 | -------------------------------------------------------------------------------- /tools/analyzer_plugin/README.md: -------------------------------------------------------------------------------- 1 | A sample command-line application with an entrypoint in `bin/`, library code 2 | in `lib/`, and example unit test in `test/`. 3 | -------------------------------------------------------------------------------- /tools/analyzer_plugin/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | analyzer: 15 | strong-mode: 16 | implicit-casts: false 17 | implicit-dynamic: false 18 | # enable-experiment: 19 | # - extension-methods 20 | errors: 21 | # treat missing required parameters as a warning (not a hint) 22 | missing_required_param: warning 23 | # treat missing returns as a warning (not a hint) 24 | missing_return: warning 25 | # allow having TODOs in the code 26 | todo: ignore 27 | # Ignore analyzer hints for updating pubspecs when using Future or 28 | # Stream and not importing dart:async 29 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 30 | sdk_version_async_exported_from_core: ignore 31 | exclude: 32 | 33 | linter: 34 | rules: 35 | # these rules are documented on and in the same order as 36 | # the Dart Lint rules page to make maintenance easier 37 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 38 | - always_declare_return_types 39 | - always_put_control_body_on_new_line 40 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 41 | - always_require_non_null_named_parameters 42 | - always_specify_types 43 | - annotate_overrides 44 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 45 | # - avoid_as # required for implicit-casts: true 46 | - avoid_bool_literals_in_conditional_expressions 47 | # - avoid_catches_without_on_clauses # we do this commonly 48 | # - avoid_catching_errors # we do this commonly 49 | # - avoid_classes_with_only_static_members 50 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 51 | - avoid_empty_else 52 | - avoid_equals_and_hash_code_on_mutable_classes 53 | - avoid_field_initializers_in_const_classes 54 | - avoid_function_literals_in_foreach_calls 55 | # - avoid_implementing_value_types # not yet tested 56 | - avoid_init_to_null 57 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 58 | - avoid_null_checks_in_equality_operators 59 | # - avoid_positional_boolean_parameters # not yet tested 60 | # - avoid_print # not yet tested 61 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 62 | # - avoid_redundant_argument_values # not yet tested 63 | - avoid_relative_lib_imports 64 | - avoid_renaming_method_parameters 65 | - avoid_return_types_on_setters 66 | # - avoid_returning_null # there are plenty of valid reasons to return null 67 | # - avoid_returning_null_for_future # not yet tested 68 | - avoid_returning_null_for_void 69 | # - avoid_returning_this # there are plenty of valid reasons to return this 70 | # - avoid_setters_without_getters # not yet tested 71 | # - avoid_shadowing_type_parameters # not yet tested 72 | - avoid_single_cascade_in_expression_statements 73 | - avoid_slow_async_io 74 | - avoid_types_as_parameter_names 75 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 76 | # - avoid_unnecessary_containers # not yet tested 77 | - avoid_unused_constructor_parameters 78 | - avoid_void_async 79 | # - avoid_web_libraries_in_flutter # not yet tested 80 | - await_only_futures 81 | - camel_case_extensions 82 | - camel_case_types 83 | - cancel_subscriptions 84 | # - cascade_invocations # not yet tested 85 | # - close_sinks # not reliable enough 86 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 87 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 88 | - control_flow_in_finally 89 | # - curly_braces_in_flow_control_structures # not yet tested 90 | # - diagnostic_describe_all_properties # not yet tested 91 | # - directives_ordering 92 | - empty_catches 93 | - empty_constructor_bodies 94 | - empty_statements 95 | # - file_names # not yet tested 96 | - flutter_style_todos 97 | - hash_and_equals 98 | - implementation_imports 99 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 100 | - iterable_contains_unrelated_type 101 | # - join_return_with_assignment # not yet tested 102 | - library_names 103 | - library_prefixes 104 | # - lines_longer_than_80_chars # not yet tested 105 | - list_remove_unrelated_type 106 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 107 | # - missing_whitespace_between_adjacent_strings # not yet tested 108 | - no_adjacent_strings_in_list 109 | - no_duplicate_case_values 110 | # - no_logic_in_create_state # not yet tested 111 | # - no_runtimeType_toString # not yet tested 112 | - non_constant_identifier_names 113 | # - null_closures # not yet tested 114 | # - omit_local_variable_types # opposite of always_specify_types 115 | # - one_member_abstracts # too many false positives 116 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 117 | - overridden_fields 118 | - package_api_docs 119 | - package_names 120 | - package_prefixed_library_names 121 | # - parameter_assignments # we do this commonly 122 | - prefer_adjacent_string_concatenation 123 | - prefer_asserts_in_initializer_lists 124 | # - prefer_asserts_with_message # not yet tested 125 | - prefer_collection_literals 126 | - prefer_conditional_assignment 127 | - prefer_const_constructors 128 | - prefer_const_constructors_in_immutables 129 | - prefer_const_declarations 130 | - prefer_const_literals_to_create_immutables 131 | # - prefer_constructors_over_static_methods # not yet tested 132 | - prefer_contains 133 | # - prefer_double_quotes # opposite of prefer_single_quotes 134 | - prefer_equal_for_default_values 135 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 136 | - prefer_final_fields 137 | - prefer_final_in_for_each 138 | - prefer_final_locals 139 | - prefer_for_elements_to_map_fromIterable 140 | - prefer_foreach 141 | # - prefer_function_declarations_over_variables # not yet tested 142 | - prefer_generic_function_type_aliases 143 | - prefer_if_elements_to_conditional_expressions 144 | - prefer_if_null_operators 145 | - prefer_initializing_formals 146 | - prefer_inlined_adds 147 | # - prefer_int_literals # not yet tested 148 | # - prefer_interpolation_to_compose_strings # not yet tested 149 | - prefer_is_empty 150 | - prefer_is_not_empty 151 | - prefer_is_not_operator 152 | - prefer_iterable_whereType 153 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 154 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 155 | # - prefer_relative_imports # not yet tested 156 | - prefer_single_quotes 157 | - prefer_spread_collections 158 | - prefer_typing_uninitialized_variables 159 | - prefer_void_to_null 160 | # - provide_deprecation_message # not yet tested 161 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 162 | - recursive_getters 163 | - slash_for_doc_comments 164 | # - sort_child_properties_last # not yet tested 165 | - sort_constructors_first 166 | # - sort_pub_dependencies 167 | - sort_unnamed_constructors_first 168 | - test_types_in_equals 169 | - throw_in_finally 170 | # - type_annotate_public_apis # subset of always_specify_types 171 | - type_init_formals 172 | # - unawaited_futures # too many false positives 173 | # - unnecessary_await_in_return # not yet tested 174 | - unnecessary_brace_in_string_interps 175 | - unnecessary_const 176 | # - unnecessary_final # conflicts with prefer_final_locals 177 | - unnecessary_getters_setters 178 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 179 | - unnecessary_new 180 | - unnecessary_null_aware_assignments 181 | - unnecessary_null_in_if_null_operators 182 | - unnecessary_overrides 183 | - unnecessary_parenthesis 184 | - unnecessary_statements 185 | #- unnecessary_string_interpolations 186 | - unnecessary_this 187 | - unrelated_type_equality_checks 188 | # - unsafe_html # not yet tested 189 | - use_full_hex_values_for_flutter_colors 190 | # - use_function_type_syntax_for_parameters # not yet tested 191 | # - use_key_in_widget_constructors # not yet tested 192 | - use_rethrow_when_possible 193 | # - use_setters_to_change_properties # not yet tested 194 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 195 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 196 | - valid_regexps 197 | - void_checks 198 | -------------------------------------------------------------------------------- /tools/analyzer_plugin/bin/plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:isolate'; 2 | 3 | import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart'; 4 | 5 | // This file must be 'plugin.dart' 6 | void main(List args, SendPort sendPort) { 7 | // for performance, default is false, if you want to check log, set it to true. 8 | CandiesAnalyzerPluginLogger().shouldLog = false; 9 | CandiesAnalyzerPluginStarter.start( 10 | args, 11 | sendPort, 12 | plugin: CandiesAnalyzerPlugin(), 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /tools/analyzer_plugin/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: candies_analyzer 2 | description: A sample command-line application. 3 | version: 1.0.0 4 | # homepage: https://www.example.com 5 | 6 | environment: 7 | sdk: '>=2.17.6 <3.0.0' 8 | 9 | dependencies: 10 | candies_analyzer_plugin: 5.0.0 11 | path: any 12 | analyzer: any 13 | analyzer_plugin: any 14 | dependency_overrides: 15 | 16 | dev_dependencies: 17 | lints: any 18 | test: any 19 | --------------------------------------------------------------------------------