pyramid_lint 1.1.0 copy "pyramid_lint: ^1.1.0" to clipboard
pyramid_lint: ^1.1.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 #

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 #

Not declaring parameter name decreases code readability and the IDEs code completion will not be able to suggest the parameter name.

  • Severity: info

Bad

typedef ItemBuilder = Widget Function(BuildContext, int);

Good

typedef ItemBuilder = Widget Function(BuildContext context, int index);

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 dynamic can be useful in certain scenarios, it sacrifices the benefits of static typing and can decrease 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

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>

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;
}

max_lines_for_file #

A file should not exceed a certain number of lines.

  • Severity: info
  • Options:
    • max_lines: int (default: 200)

max_lines_for_function #

A function should not exceed a certain number of lines.

  • Severity: info
  • Options:
    • max_lines: int (default: 100)

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_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
  • orientation
  • padding
  • platformBrightness
  • size
  • systemGestureInsets
  • textScaleFactor
  • viewInsets
  • viewPadding

Bad

final size = MediaQuery.of(context).size;
final orientation = MediaQuery.maybeOf(context)?.orientation;

Good

final size = MediaQuery.of(context).size;
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.

invert_boolean_expression

swap_then_else_expression #

Swap the then and else expression of a if-else expression or conditional expression.

swap_then_else_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.

use_edge_insets_zero

wrap_with_expanded #

Wrap the selected widget with an Expanded.

wrap_with_expanded

wrap_with_layout_builder #

Wrap the selected widget with a LayoutBuilder.

wrap_with_layout_builder

wrap_with_stack #

Wrap the selected widget with a Stack.

wrap_with_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.

7
likes
0
pub points
72%
popularity

Publisher

unverified uploader

Linting tool for Dart and Flutter projects to encourage good coding practices.

Repository (GitHub)
View/report issues

Topics

#lints #analyzer

License

unknown (license)

Dependencies

analyzer, analyzer_plugin, collection, custom_lint_builder

More

Packages that depend on pyramid_lint