Flutter Static Code Analysis Tools To Pair For DevOPS
For DevOPS you should pair both lints and code metrics together as part of your flutter devOPS.
Compilers are lazy and thus the efforts we have to manually do to move run time exceptions to compilable executable code involves some static analysis to get warnings when our code sucks and needs to be changed. The other aspect is getting some code metrics to make the right architecture design decisions.
The two tools I will show to pair up in static code analysis is Pascal's lint package and Dart Code Linter. Pascal's lint package fully implements the Effective Dart best practices. While the Dart Code Linter package implements code metrics to measures to be used in architecture code choice decisions.
Pascal's Lint Package
The myth is that the Flutter Lints package that is the default for Flutter projects fully covers the guidelines in Effective Dart guide:
The Flutter Lints package only covers 10 percent of the Effective Dart guide, but the Dart SDK and Flutter SDK teams have created more lints that do in fact cover the rest of Effective Dart guidance. Pascal has curated that full list of both Dart and Flutter Lints into a package called Lint:
One includes the strict lint import at the top of the analysis options YAML file and includes the dependency in the dev dependencies block in the pubspec YAML file.
Now I use the Dart Code Linter to add more lint rules and code metrics to the static analysis.
Dart Code Linter
Dart Code Linter is a soft fork of Dart Code Metrics when it was an open source project.
### It has BIN component beyond the package, so one has to put the dependency in the dev dependencies block and in the terminal type the below command set and once the DCL settings is put in the analysis options file then in the terminal you will type the below command set:
To activate in terminal type | |
dart pub global activate dart_code_linter |
# This file configures the analyzer, which statically analyzes Dart code to | |
# check for errors, warnings, and lints. | |
# | |
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled | |
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be | |
# invoked from the command line by running `flutter analyze`. | |
# The following line activates a set of recommended lints for Flutter apps, | |
# packages, and plugins designed to encourage good coding practices. | |
# Lint package has three, strict, casual and package | |
include: package:lint/strict.yaml | |
analyzer: | |
# for freezed when json serializable is used | |
errors: | |
invalid_annotation_target: ignore | |
exclude: | |
- "**/*.g.dart" | |
- "**.freezed.dart" | |
- "build/**" | |
- "lib/generated/**" | |
plugins: | |
- dart_code_linter | |
# as of 10 13 23 they have not documented | |
# config settings for certain rules so | |
# those rules are not included for now | |
dart_code_linter: | |
# I do not use extends but have implemented my | |
# own extra lint rule list | |
anti-patterns: | |
- long-method | |
- long-parameter-list | |
metrics: | |
cyclomatic-complexity: 20 | |
halstead-volume: 150 | |
maintainability-index: 50 | |
maximum-nesting-level: 5 | |
number-of-parameters: 4 | |
number-of-methods: 10 | |
source-lines-of-code: 250 | |
technical-debt: | |
threshold: 1 | |
todo-cost: 161 | |
ignore-cost: 320 | |
ignore-for-file-cost: 396 | |
as-dynamic-cost: 322 | |
deprecated-annotations-cost: 37 | |
file-nullsafety-migration-cost: 41 | |
unit-type: "USD" | |
metrics-exclude: | |
- test/** | |
# full rules list is at | |
# https://github.com/bancolombia/dart-code-linter/wiki/Rules | |
rules: | |
#dart | |
- avoid-substring | |
# used with sort_child_properties_last | |
- arguments-ordering: | |
child-last: true | |
- avoid-banned-file-names: | |
entries: | |
- .*example.dart | |
# these are used to help enforce arch decisions | |
# - avoid-banned-imports | |
# - avoid-banned-types | |
- avoid-cascade-after-if-null | |
- avoid-collection-methods-with-unrelated-types | |
- avoid-duplicate-exports | |
- avoid-dynamic | |
- avoid-global-state | |
# - avoid-ignoring-return-values | |
- avoid-importing-entrypoint-exports: | |
only-in-src: true | |
# - avoid-late-keyword: | |
# allow-initialized: true | |
- avoid-long-files: | |
max-length: 500 | |
- avoid-long-functions: | |
max-length: 80 | |
ignored-names: | |
- some | |
- name | |
- avoid-long-parameter-list: | |
ignore-optional: true | |
max-amount: 3 | |
ignored-names: | |
- some | |
- name | |
- avoid-long-records: | |
max-amount: 6 | |
- avoid-missing-enum-constant-in-map | |
- avoid-nested-conditional-expressions | |
- avoid-nested-records: | |
acceptable-level: 2 | |
- avoid-nested-switch-expressions: | |
acceptable-level: 1 | |
- avoid-non-ascii-symbols | |
# - avoid-non-null-assertion: | |
# skip-checked-fields: true | |
- avoid-nullable-interpolation: | |
ignored-invocations: | |
- log | |
- debugPrint | |
# - avoid-passing-async-when-sync-expected | |
# - avoid-redundant-async | |
- avoid-shadowing: | |
ignore-static: true | |
ignore-fields: true | |
ignore-parameters: true | |
ignored-names: | |
- some | |
- name | |
- avoid-similar-names: | |
show-similarity: true | |
similarity-threshold: 0.9 | |
ignored-names: | |
- some | |
- name | |
- avoid-throw-in-catch-block | |
- avoid-top-level-members-in-tests | |
- avoid-unnecessary-type-assertions | |
- avoid-unnecessary-type-casts | |
- avoid-unrelated-type-assertions: | |
ignore-mixins: false | |
- avoid-unused-instances: | |
ignored-instances: | |
- Timer | |
- SomeOtherClass | |
- avoid-unused-parameters: | |
ignore-inline-functions: false | |
# - ban-name | |
# - banned-usage | |
- binary-expression-operand-order | |
- double-literal-format | |
- format-comment: | |
only-doc-comments: true | |
ignored-patterns: | |
- ^ cSpell.* | |
# - match-class-name-pattern: | |
# entries: | |
# - path: '.*state.dart' | |
# pattern: 'State$' | |
# ignore-private: true | |
- missing-test-assertion: | |
include-assertions: | |
- verify | |
include-methods: | |
- customTest | |
- move-records-to-typedefs: | |
min-fields: 5 | |
min-occurrences: 3 | |
- newline-before-return | |
- no-boolean-literal-compare: | |
allow-false: true | |
# - no-empty-block | |
- no-equal-arguments: | |
ignored-parameters: | |
- height | |
- width | |
- no-equal-then-else | |
# - no-magic-number: | |
# allowed: [3.14, 100, 12] | |
# allow-only-once: true | |
- no-object-declaration | |
- parameters-ordering: | |
required: 'first' | |
default: 'last' | |
- prefer-async-await | |
- prefer-commenting-analyzer-ignores | |
- prefer-conditional-expressions: | |
ignore-nested: true | |
- prefer-correct-error-name: | |
allowed-names: | |
- err | |
# - prefer-correct-identifier-length: | |
# exceptions: [ 'a' ] | |
# max-identifier-length: 30 | |
# min-identifier-length: 4 | |
- prefer-correct-switch-length: | |
min-length: 5 | |
max-length: 20 | |
- prefer-correct-test-file-name | |
- prefer-correct-type-name: | |
excluded: [ 'exampleExclude' ] | |
min-length: 3 | |
max-length: 40 | |
- prefer-declaring-const-constructor: | |
allow-one: true | |
- prefer-enums-by-name | |
- prefer-first | |
- prefer-immediate-return | |
- prefer-iterable-of | |
- prefer-last | |
- prefer-match-file-name | |
- prefer-moving-to-variable: | |
allowed-duplicated-chains: 3 | |
- prefer-named-boolean-parameters: | |
ignore-single: true | |
ignore-single-boolean: true | |
- prefer-static-class | |
- prefer-trailing-comma: | |
break-on: 2 | |
- unnecessary-trailing-comma: | |
max-width: 100 | |
- avoid-double-slash-imports | |
- avoid-unnecessary-conditionals | |
# flutter | |
- prefer-define-hero-tag | |
- always-remove-listener | |
- avoid-border-all | |
- avoid-missing-image-alt: | |
allow-empty: true | |
- avoid-returning-widgets: | |
ignored-names: | |
- testFunction | |
ignored-annotations: | |
- allowedAnnotation | |
- avoid-shrink-wrap-in-lists | |
- avoid-expanded-as-spacer | |
- avoid-wrapping-in-padding | |
- check-for-equals-in-render-object-setters | |
- consistent-update-render-object | |
- dispose-fields: | |
ignore-blocs: false | |
- prefer-const-border-radius | |
- prefer-correct-edge-insets-constructor | |
# - prefer-extracting-callbacks | |
- prefer-single-widget-per-file | |
- prefer-using-list-view | |
- use-setstate-synchronously | |
# Intl, uncomment when needed | |
# - prefer-intl-name | |
# - prefer-provide-intl-description | |
# - provide-correct-intl-args | |
- member-ordering: | |
order: | |
- constructors | |
- public-fields | |
- private-fields | |
- close-method | |
- dispose-method | |
widgets-order: | |
- constructor | |
- build-method | |
- init-state-method | |
- did-change-dependencies-method | |
- did-update-widget-method | |
- dispose-method | |
linter: | |
# The lint rules applied to this project can be customized in the | |
# section below to disable rules from the `package:flutter_lints/flutter.yaml` | |
# included above or to enable additional rules. A list of all available lints | |
# and their documentation is published at | |
# https://dart-lang.github.io/linter/lints/index.html. | |
# | |
# Instead of disabling a lint rule for the entire project in the | |
# section below, it can also be suppressed for a single line of code | |
# or a specific dart file by using the `// ignore: name_of_lint` and | |
# `// ignore_for_file: name_of_lint` syntax on the line or in the file | |
# producing the lint. | |
rules: | |
# avoid_print: false # Uncomment to disable the `avoid_print` rule | |
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule | |
# Additional information about this file can be found at | |
# https://dart.dev/guides/language/analysis-options |
name: metrics_lint_demo | |
description: "A new Flutter project." | |
# Prevent accidental publishing to pub.dev. | |
publish_to: 'none' | |
version: 1.0.0+1 | |
environment: | |
sdk: '>=3.2.0-210.2.beta <4.0.0' | |
dependencies: | |
flutter: | |
sdk: flutter | |
flutter_localizations: | |
sdk: flutter | |
dev_dependencies: | |
flutter_test: | |
sdk: flutter | |
flutter_launcher_icons: "^0.13.1" | |
# more opinionated lints than flutter lints | |
lint: ^2.1.2 | |
# replaces DCM | |
dart_code_linter: ^1.1.1 | |
flutter: | |
uses-material-design: true | |
# Enable generation of localized Strings from arb files. | |
generate: true | |
assets: | |
# Add assets from the images directory to the application. | |
- assets/images/ |
after creating reports folder in app project in | |
terminal type | |
dart run dart_code_linter:metrics analyze lib -o reports/code_metrics -r html |
And that will produce these reports that then we can use to make code architecture decisions:
Index HTML Code Metrics Report
Thoughts
The general idea here is to through the whole devOPS stack including testing so that I can do app builds the Google way where one only uses actual targeted devices during the final release builds of the app through CI services like CodeMagic. Or to put it another way testing is focused on non-instrumented and non-integration testing. And of course you can combine that with using a desktop tower Intel 13900K or 14900K and get Apple M3 build times without paying the Apple tax.
Fred Grott's Newsletter
Code: Flutter Bytes Hub
Demos-at-YouTube: My YouTube Channel
An ADHD'ers super focus on Flutter app design and the side hustle of designing flutter apps. Delivered, several days every weekday to your inbox.