pyramid_lint 1.2.0 pyramid_lint: ^1.2.0 copied to clipboard
Linting tool for Dart and Flutter projects to encourage good coding practices.
Linting tool for Dart and Flutter projects.
Pyramid Lint #
Pyramid Lint is a linting tool built with custom_lint. It offers a set of additional lints and quick fixes to help developers enforce a consistent coding style, prevent common mistakes and enhance code readability.
Table of contents #
- Installation
- Configuration
- Dart lints
- always_declare_parameter_names
- avoid_abbreviations_in_doc_comments
- avoid_duplicate_import
- avoid_dynamic
- avoid_empty_blocks
- avoid_inverted_boolean_expressions
- avoid_unused_parameters
- boolean_prefix
- doc_comments_before_annotations
- max_lines_for_file
- max_lines_for_function
- prefer_async_await
- prefer_declaring_const_constructors
- prefer_immediate_return
- prefer_iterable_first
- prefer_iterable_last
- prefer_library_prefixes
- prefer_new_line_before_return
- prefer_underscore_for_unused_callback_parameters
- unnecessary_nullable_return_type
- Flutter lints
- avoid_returning_widgets
- avoid_single_child_in_flex
- prefer_async_callback
- prefer_border_from_border_side
- prefer_border_radius_all
- prefer_dedicated_media_query_method
- prefer_spacer
- prefer_text_rich
- proper_controller_dispose
- proper_edge_insets_constructor
- proper_expanded_and_flexible
- proper_from_environment
- proper_super_dispose
- proper_super_init_state
- Dart assists
- Flutter assists
- Contributing
- License
Installation #
Run the following command to add custom_lint
and pyramid_lint
to your project's dev dependencies:
dart pub add --dev custom_lint pyramid_lint
Then enable custom_lint
in your analysis_options.yaml
file. You can also exclude generated files from analysis.
analyzer:
plugins:
- custom_lint
exclude:
- "**.freezed.dart"
- "**.g.dart"
- "**.gr.dart"
Configuration #
By default, all lints are enabled. To disable a specific lint, add the following to your analysis_options.yaml
file:
custom_lint:
rules:
- specific_lint_rule: false # disable specific_lint_rule
If you prefer to disable all lints and only enable specific ones, you can edit your analysis_options.yaml file as below:
custom_lint:
enable_all_lint_rules: false # disable all lints
rules:
- specific_lint_rule # enable specific_lint_rule
Some lints are configurable. To configure a lint, follow the example below:
custom_lint:
rules:
- configurable_lint_rule:
option1: value1
option2: value2
Dart lints #
always_declare_parameter_names #
Parameter names should always be declared to enhance code readability and enable IDEs to provide code completion suggestions.
- Severity: info
Bad
typedef ItemBuilder = Widget? Function(BuildContext, int);
// IDE's code completion with default parameter names p0, p1, ...
itemBuilder: (p0, p1) {},
Good
typedef ItemBuilder = Widget? Function(BuildContext context, int index);
// IDE's code completion with descriptive parameter names
itemBuilder: (context, index) {},
avoid_abbreviations_in_doc_comments #
Avoid using abbreviations in doc comments as they can hinder readability and cause confusion for readers.
- Severity: warning
- Options:
- abbreviations:
List<String>
- abbreviations:
custom_lint:
rules:
- avoid_abbreviations_in_doc_comments:
abbreviations: ['approx.']
see: effective-dart
Included abbreviations:
- e.g.
- i.e.
- etc.
- et al.
Bad
/// This is documentation.
///
/// e.g. ...
int function(){}
Good
/// This is documentation.
///
/// For example ...
int function(){}
avoid_duplicate_import #
Duplicate imports can lead to confusion.
- Severity: warning
Bad
import 'dart:math' as math show max;
import 'dart:math';
final a = math.max(1, 10);
final b = min(1, 10);
Good
import 'dart:math' as math show max, min;
final a = math.max(1, 10);
final b = math.min(1, 10);
avoid_dynamic #
While the dynamic
type can be useful in certain scenarios, it sacrifices the benefits of static typing and decreases code readability. Using dynamic
with Map
will not trigger this lint.
- Severity: info
Bad
dynamic thing = 'text';
void log(dynamic something) => print(something);
List<dynamic> list = [1, 2, 3];
final setLiteral = <dynamic>{'a', 'b', 'c'};
Good
String thing = 'text';
void log(String something) => print(something);
List<int> list = [1, 2, 3];
final setLiteral = <String>{'a', 'b', 'c'};
avoid_empty_blocks #
Empty block usually indicates a missing implementation.
- Severity: warning
Bad
void doSomething() {}
Good
void doSomething() {
actuallyDoSomething();
}
void doSomething() {
// TODO: implement doSomething
}
avoid_inverted_boolean_expressions #
Unnecessary inverted boolean expression should be avoided since it decreases code readability.
- Severity: info
- Quick fix: ✓
Bad
if (!(number > 0)) {}
final text = !(number == 0) ? 'Not zero' : 'Zero';
Good
if (number <= 0) {}
final text = number != 0 ? 'Not zero' : 'Zero';
avoid_unused_parameters #
Unused parameters should be removed to avoid confusion.
- Severity: warning
- Options:
- excluded_parameters:
List<String>
- excluded_parameters:
custom_lint:
rules:
- avoid_unused_parameters:
excluded_parameters: ['ref']
Bad
void log(String a, String b) {
print(a);
}
Good
void log(String a) {
print(a);
}
boolean_prefix #
Boolean variables, functions and getters that return a boolean value should be prefixed with specific verbs.
- Severity: info
- Options:
- valid_prefixes:
List<String>
- valid_prefixes:
custom_lint:
rules:
- boolean_prefix:
valid_prefixes: ['...']
Default valid prefixes:
- is
- are
- was
- were
- has
- have
- had
- can
- should
- will
- do
- does
- did
see: effective-dart
Bad
class Point {
final double x;
final double y;
const Point(this.x, this.y);
bool get origin => x == 0 && y == 0;
bool samePoint(Point other) => x == other.x && y == other.y;
}
Good
class Point {
final double x;
final double y;
const Point(this.x, this.y);
bool get isOrigin => x == 0 && y == 0;
bool isSamePoint(Point other) => x == other.x && y == other.y;
}
doc_comments_before_annotations #
Doc comments should be placed before annotations.
- Severity: info
- Quick fix: ✓
see: effective-dart
Bad
@immutable
/// Some documentation.
class A {}
Good
/// Some documentation.
@immutable
class A {}
max_lines_for_file #
A file should not exceed a certain number of lines.
- Severity: info
- Options:
- max_lines:
int
(default: 200)
- max_lines:
custom_lint:
rules:
- max_lines_for_file:
max_lines: 500
max_lines_for_function #
A function should not exceed a certain number of lines.
- Severity: info
- Options:
- max_lines:
int
(default: 100)
- max_lines:
custom_lint:
rules:
- max_lines_for_function:
max_lines: 200
prefer_async_await #
Using async/await improves code readability.
see: effective-dart
- Severity: info
Bad
void fetchData() {
performAsyncOperation().then((result) {
print(result);
}).catchError((Object? error) {
print('Error: $error');
}).whenComplete(() {
print('Done');
});
}
Good
Future<void> fetchData() async {
try {
final result = await performAsyncOperation();
print(result);
} catch (error) {
print('Error: $error');
} finally {
print('Done');
}
}
prefer_declaring_const_constructors #
Constructors of classes with only final fields should be declared as const constructors when possible.
- Severity: info
- Quick fix: ✓
Bad
class Point {
final double x;
final double y;
Point(this.x, this.y);
Point.origin()
: x = 0,
y = 0;
}
Good
class Point {
final double x;
final double y;
const Point(this.x, this.y);
const Point.origin()
: x = 0,
y = 0;
}
prefer_immediate_return #
By directly returning the expression instead of assigning it to a variable and then returning the variable, you can simplify the code and improve readability.
- Severity: info
- Quick fix: ✓
Bad
int add(int a, int b) {
final result = a + b;
return result;
}
Good
int add(int a, int b) {
return a + b;
}
prefer_iterable_first #
Using iterable.first
increases code readability.
- Severity: info
- Quick fix: ✓
Bad
const numbers = [1, 2, 3];
final firstNumber = numbers[0];
// or
final firstNumber = numbers.elementAt(0);
Good
const numbers = [1, 2, 3];
final firstNumber = numbers.first;
prefer_iterable_last #
Using iterable.last
increases code readability.
- Severity: info
- Quick fix: ✓
Bad
const numbers = [1, 2, 3];
final lastNumber = numbers[numbers.length - 1];
// or
final lastNumber = numbers.elementAt(numbers.length - 1);
Good
const numbers = [1, 2, 3];
final lastNumber = numbers.last;
prefer_library_prefixes #
Use library prefixes to avoid name conflicts and increase code readability.
- Severity: info
- Quick fix: ✓
- Options:
- include_default_libraries:
bool
(default: true) - libraries:
List<String>
- include_default_libraries:
custom_lint:
rules:
- prefer_library_prefixes:
include_default_libraries: false
libraries: ['package:http/http.dart']
Default libraries:
- 'dart:developer'
- 'dart:math'
Bad
import 'dart:developer';
import 'dart:math';
print(log(e));
log('message');
import 'package:http/http.dart';
final response = await post(...);
Good
import 'dart:developer' as developer;
import 'dart:math' as math;
print(math.log(math.e));
developer.log('message');
import 'package:http/http.dart' as http;
final response = await http.post(...);
prefer_new_line_before_return #
Adding a new line before the return statement increases code readability.
- Severity: info
- Quick fix: ✓
Bad
if (...) {
...
return ...;
}
return ...;
Good
if (...) {
...
return ...;
}
return ...;
prefer_underscore_for_unused_callback_parameters #
Using _
for unused callback parameters clearly convey that the parameter is intentionally ignored within the code block. If the function has multiple unused parameters, use additional underscores such as __
, ___
.
see: effective-dart
- Severity: info
Bad
itemBuilder: (context, index) {
return Text('Item $index');
}
Good
itemBuilder: (_, index) {
return Text('Item $index');
}
unnecessary_nullable_return_type #
Declaring functions or methods with a nullable return type is unnecessary when the return value is never null.
- Severity: warning
- Quick fix: ✓
Bad
int? sum(int a, int b) {
return a + b;
}
Good
int sum(int a, int b) {
return a + b;
}
Flutter lints #
avoid_returning_widgets #
Returning widgets is not recommended for performance reasons.
- Severity: info
Bad
class A extends StatelessWidget {
const A({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
_buildWidget(),
_buildWidget(),
],
);
}
Widget _buildWidget() {
return ...;
}
}
Good
class A extends StatelessWidget {
const A({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
B(),
B(),
],
);
}
}
class B extends StatelessWidget {
const B({super.key});
@override
Widget build(BuildContext context) {
return ...;
}
}
avoid_single_child_in_flex #
Using Column or Row with only one child is inefficient.
- Severity: info
- Quick fix: ✓
Bad
Row(
children: [
Placeholder(),
],
)
Good
Align(
child: Placeholder(),
)
// or
Center(
child: Placeholder(),
)
prefer_async_callback #
Using the built-in typedef AsyncCallback
increases code readability.
- Severity: info
- Quick fix: ✓
Bad
final Future<void> Function() cb;
Good
final AsyncCallback cb;
prefer_border_from_border_side #
Border.all
is not a const constructor and it uses const constructor Border.fromBorderSide
internally.
- Severity: info
- Quick fix: ✓
Bad
DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
width: 1,
style: BorderStyle.solid,
),
),
)
Good
const DecoratedBox(
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(
width: 1,
style: BorderStyle.solid,
),
),
),
)
prefer_border_radius_all #
BorderRadius.all
is not a const constructor and it uses const constructor BorderRadius.circular
internally.
- Severity: info
- Quick fix: ✓
Bad
DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
)
Good
const DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
)
prefer_dedicated_media_query_method #
Using MediaQuery.of(context) to access below properties will cause unnecessary rebuilds.
- Severity: info
- Quick fix: ✓
Properties:
- accessibleNavigation
- alwaysUse24HourFormat
- boldText
- devicePixelRatio
- disableAnimations
- displayFeatures
- gestureSettings
- highContrast
- invertColors
- navigationMode
- onOffSwitchLabels
- orientation
- padding
- platformBrightness
- size
- systemGestureInsets
- textScaleFactor (deprecated after v3.12.0-2.0.pre)
- textScaler
- viewInsets
- viewPadding
Bad
final size = MediaQuery.of(context).size;
final orientation = MediaQuery.maybeOf(context)?.orientation;
Good
final size = MediaQuery.sizeOf(context);
final orientation = MediaQuery.maybeOrientationOf(context);
prefer_spacer #
Using Expanded with an empty Container or SizedBox is inefficient.
- Severity: info
- Quick fix: ✓
Bad
Column(
children: [
Expanded(
flex: 2,
child: SizedBox(),
),
],
)
Good
Column(
children: [
Spacer(flex: 2),
],
)
prefer_text_rich #
RichText
does not inherit TextStyle from DefaultTextStyle.
- Severity: info
- Quick fix: ✓
Bad
RichText(
text: const TextSpan(
text: 'Pyramid',
children: [
TextSpan(text: 'Lint'),
],
),
)
Good
const Text.rich(
TextSpan(
text: 'Pyramid',
children: [
TextSpan(text: 'Lint'),
],
),
)
prefer_value_changed #
Using the built-in typedef ValueChanged
increases code readability.
- Severity: info
- Quick fix: ✓
Bad
final void Function(int) cb;
Good
final ValueChanged<int> cb;
prefer_void_callback #
Using the built-in typedef VoidCallback
increases code readability.
- Severity: info
- Quick fix: ✓
Bad
final void Function() cb;
Good
final VoidCallback cb;
proper_controller_dispose #
Controllers should be disposed in dispose method to avoid memory leaks.
- Severity: error
- Quick fix: ✓
Controllers:
- AnimationController
- PageController
- ScrollController
- SearchController
- TabController
- TextEditingController
- UndoHistoryController
Bad
class _MyWidgetState extends State<MyWidget> {
final _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Placeholder();
}
}
Good
class _MyWidgetState extends State<MyWidget> {
final _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Placeholder();
}
@override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
}
proper_edge_insets_constructor #
Using correct EdgeInsets constructor increases code readability.
- Severity: info
- Quick fix: ✓
Bad
padding = const EdgeInsets.fromLTRB(8, 8, 8, 8);
padding = const EdgeInsets.fromLTRB(8, 0, 8, 0);
padding = const EdgeInsets.fromLTRB(8, 4, 8, 4);
padding = const EdgeInsets.fromLTRB(8, 4, 8, 0);
padding = const EdgeInsets.only(left: 8, top: 8, right: 8, bottom: 8);
padding = const EdgeInsets.only(left: 8, top: 0, right: 8, bottom: 0);
padding = const EdgeInsets.only(left: 8, top: 4, right: 8, bottom: 4);
padding = const EdgeInsets.only(left: 2, top: 4, right: 6, bottom: 8);
padding = const EdgeInsets.symmetric(horizontal: 0, vertical: 0);
padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 8);
padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 0);
Good
padding = const EdgeInsets.all(8);
padding = const EdgeInsets.symmetric(horizontal: 8);
padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 4);
padding = const EdgeInsets.only(left: 8, top: 4, right: 8);
padding = const EdgeInsets.all(8);
padding = const EdgeInsets.symmetric(horizontal: 8);
padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 4);
padding = const EdgeInsets.fromLTRB(2, 4, 6, 8);
padding = const EdgeInsets.all(8);
padding = const EdgeInsets.symmetric(horizontal: 8);
proper_expanded_and_flexible #
Expanded and Flexible should be placed inside a Row, Column, or Flex.
- Severity: error
Bad
Center(
child: Expanded(
child: Container(),
),
)
Good
Row(
children: [
Expanded(
child: Container(),
),
],
)
proper_from_environment #
The constructors bool.fromEnvironment
, int.fromEnvironment
and String.fromEnvironment
are only guaranteed to work when invoked as a const
constructor.
see: dart-lang-issue
- Severity: error
- Quick fix: ✓
Bad
final boolean = bool.fromEnvironment('bool');
int integer = int.fromEnvironment('int');
var string = String.fromEnvironment('String');
Good
const boolean = bool.fromEnvironment('bool');
const integer = int.fromEnvironment('int');
const string = String.fromEnvironment('String');
proper_super_dispose #
super.dispose()
should be called at the end of the dispose method.
- Severity: error
- Quick fix: ✓
Bad
@override
void dispose() {
super.dispose();
_dispose();
}
Good
@override
void dispose() {
_dispose();
super.dispose();
}
proper_super_init_state #
super.initState()
should be called at the start of the initState method.
- Severity: error
- Quick fix: ✓
Bad
@override
void initState() {
_init();
super.initState();
}
Good
@override
void initState() {
super.initState();
_init();
}
Dart assists #
invert_boolean_expression #
Invert a boolean expression.
swap_then_else_expression #
Swap the then and else expression of a if-else expression or conditional expression.
Flutter assists #
use_edge_insets_zero #
Replace
- EdgeInsets.all(0)
- EdgeInsets.fromLTRB(0, 0, 0, 0)
- EdgeInsets.only(left: 0, top: 0, right: 0, bottom: 0)
- EdgeInsets.symmetric(horizontal: 0, vertical: 0)
with EdgeInsets.zero.
wrap_with_expanded #
Wrap the selected widget with an Expanded.
wrap_with_layout_builder #
Wrap the selected widget with a LayoutBuilder.
wrap_with_stack #
Wrap the selected widget with a Stack.
Contributing #
Contributions are appreciated! You can contribute by:
- Creating an issue to report a bug or suggest a new feature.
- Submitting a pull request to fix a bug or implement a new feature.
- Improving the documentation.
To get started contributing to Pyramid Lint, please refer to the Contributing guide.
License #
Pyramid Lint is licensed under the MIT License.