Dart Code Linter (DCL)
website·Indexed 36 days ago
https://dcl.apps.bancolombia.com/DCL is a tool designed to improve the quality and consistency of Dart code. It provides a comprehensive set of lint rules across Dart, Flutter, Flame, and Intl, covering categories such as performance, style, and warnings to help developers maintain high coding standards.
What's inside DCL
- DCL is a static analysis toolkit designed to identify and fix problems in Dart and Flutter code, including potential runtime bugs, best practice violations, and styling issues. It provides over 70 built-in rules for code validation and allows for custom configurations to meet specific project needs.
Enforce identifier length constraints with prefer-correct-identifier-length
Theprefer-correct-identifier-lengthrule ensures that variable names in classes, functions, extensions, and mixins, as well as enum values, fall within a specified length range. This prevents identifiers from being too cryptic (too short) or excessively verbose (too long).Enforce member ordering in Dart classes
Themember-orderingrule (added in v1.0.0) enforces a specific sequence for class members. It supports separate configurations for regular classes via theorderproperty and Flutter widget classes via thewidgets-orderproperty. Members are grouped by patterns (e.g., visibility, modifiers, and type) or by specific method/field names.Understand and calculate the Maintainability Index metric
The Maintainability Index (MI) is a software metric that measures how easy source code is to support and change. It is calculated using a factored formula based on three other metrics: Source Lines Of Code (SLOC), Cyclomatic Complexity (CYCLO), and Halstead Volume (HALVOL).
The linter uses the Microsoft Visual Studio derivative version, which shifts the scale to a range of 0 to 100:
MI = max(0, (171 − 5.2 * log(HALVOL) − 0.23 * log(CYCLO) − 16.2 * log(SLOC)) * 100 / 171)Note: This metric is considered experimental and should be weighted less heavily than other metrics.
Understand and configure the Source Lines of Code (SLOC) metric
The Source Lines of Code (SLOC) metric provides an approximate count of source code lines within a function or method, specifically excluding blank lines and comment lines. It is used to estimate program size, development effort, programming productivity, and long-term maintainability.Configure the avoid-late-keyword lint rule
The
avoid-late-keywordrule can be customized via thedart_code_linterconfiguration to allow specific exceptions:allow-initialized(default:false): When set totrue, the linter will not warn aboutlatevariables that are initialized at the point of declaration.ignored-types(default:[AnimationController]): A list of types that are permitted to use thelatekeyword without triggering a warning.
dart_code_linter: rules: avoid-late-keyword: allow-initialized: trueUse byName to retrieve Dart enum values by string
Since Dart 2.15, you should use the
byNamemethod on enum values to retrieve an enum member by its string name. This is more concise than usingfirstWhereto match thenameproperty.Caveat: The
byNamemethod will throw an exception if the enum does not contain a value matching the provided string name.// Avoid this (manual search) final styleDefinition = StyleDefinition.values.firstWhere( (def) => def.name == json['styleDefinition'], ); // Do this (using byName) final styleDefinition = StyleDefinition.values.byName(json['styleDefinition']);Prevent double slashes in Dart import and export directives
Theavoid-double-slash-importslint rule (introduced in version 1.0.0) warns when animport,export, orpartdirective contains a double slash (//) in the URI. While double slashes are technically valid in URIs, they can cause the program to fail to run in certain environments. This rule ensures paths are clean and consistent.// ❌ Bad: Contains double slashes import 'package:test//material.dart'; import '../../..//rule_utils_test.dart'; export 'package:mocktail//good_file.dart'; part '../../..//individual/rules/empty.dart'; // ✅ Good: Single slashes used import 'package:test/material.dart'; import '../../../rule_utils_test.dart'; export 'package:mocktail/good_file.dart'; part '../../../individual/rules/empty.dart';Avoid using mutable global state in Dart
Theavoid-global-staterule prevents the use of top-level variables and static class members that are neitherfinalnorconst. Mutable global state is discouraged because it distributes application state across multiple files, leaves state unprotected from modification anywhere in the app, and increases debugging complexity. Developers should use dedicated state management solutions instead of mutable global variables.// ❌ Bad: Mutable top-level and static variables var answer = 42; var evenNumbers = [1, 2, 3].where((element) => element.isEven); class Foo { static int? bar; } // ✅ Good: Use const or final for top-level variables const answer = 42; final evenNumbers = [1, 2, 3].where((element) => element.isEven); class Foo { // Static variables should be initialized or final to avoid mutable global state static int bar = 42; }Enforce single widget per file with prefer-single-widget-per-file
Theprefer-single-widget-per-filerule warns when a Dart file contains more than one widget. This ensures that each widget is isolated in its own file, adhering to the single responsibility principle.// some_widget.dart class SomeWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Text('Single widget per file'); } }Use async/await instead of .then() for Future results
Theprefer-async-awaitlint recommends using theasync/awaitsyntax to handleFutureresults instead of using.then()callbacks. Usingawaitimproves readability and prevents common errors associated with mixingawaitand.then(), as awaiting a.then()call only awaits the completion of the callback itself rather than the original Future's intended flow.// Bad: Using .then() callbacks Future<void> main() async { someFuture.then((result) => handleResult(result)); await (foo.asyncMethod()).then((result) => handleResult(result)); } // Good: Using async/await Future<void> main() async { final result = await someFuture; handleResult(result); final anotherResult = await foo.asyncMethod(); handleResult(anotherResult); }Avoid initializing late final variables in onMount
Do not initializelate finalvariables inside a Component'sonMountmethod. Because components may be removed and re-added to the UI tree, attempting to re-initialize alate finalvariable during a subsequentonMountcall will trigger a runtime exception. To fix this, use a non-final variable if the value needs to be assigned during the mount phase.// ❌ Bad: late final will throw an exception if the component is remounted class MyComponent extends Component { late final int x; @override void onMount() { x = 1; // LINT: avoid-initializing-in-on-mount } } // ✅ Good: Use a regular variable for values initialized in onMount class MyComponent extends Component { int x; @override void onMount() { x = 1; } }