Skip to main content

LeanCode Lint

An opinionated set of high-quality, robust, and up-to-date lint rules used at LeanCode. This package provides custom lint rules, assists, and a comprehensive analyzer configuration for Flutter and Dart projects.

Installation

1

Add dependency

Add leancode_lint as a dev dependency in your project’s pubspec.yaml:
dart pub add leancode_lint --dev
2

Configure analysis_options.yaml

Include the package in your analysis_options.yaml and enable the analyzer plugin:
analysis_options.yaml
include: package:leancode_lint/analysis_options.yaml

plugins:
  leancode_lint: ^21.0.0

analyzer:
  exclude:
    - '**/*.g.dart'
3

Restart analyzer

Run flutter pub get in your project main directory and restart the analysis server in your IDE.
For library packages (rather than applications), use package:leancode_lint/analysis_options_package.yaml instead to expose public API documentation requirements.

Configuration

Default Configuration

By default, leancode_lint uses LeanCodeLintConfig() with sensible defaults. Simply enable the plugin in analysis_options.yaml to use it as-is.

Custom Configuration

To customize rule behavior, create your own analyzer plugin package that depends on leancode_lint:
import 'package:leancode_lint/plugin.dart';

final plugin = LeanCodeLintPlugin(
  name: 'my_lints',
  config: LeanCodeLintConfig(
    applicationPrefix: 'Lncd',
    catchParameterNames: CatchParameterNamesConfig(
      exception: 'error',
      stackTrace: 'stackTrace',
    ),
    designSystemItemReplacements: {
      'AppText': [
        DesignSystemForbiddenItem(name: 'Text', packageName: 'flutter'),
        DesignSystemForbiddenItem(name: 'RichText', packageName: 'flutter'),
      ],
      'AppScaffold': [
        DesignSystemForbiddenItem(name: 'Scaffold', packageName: 'flutter'),
      ],
    },
  ),
);

Custom Lint Rules

leancode_lint includes 16 custom lint rules to enforce best practices:
DO add a ‘Cubit’ suffix to your cubit names.Bad:
class MyClass extends Cubit<int> {}
Good:
class MyClassCubit extends Cubit<int> {}
Configuration: None.
AVOID using hooks conditionally. Hooks must be called in the same order on every build.Bad:
Widget build(BuildContext context) {
  if (condition) {
    useEffect(() {
      // ...
    }, []);
  }
}
Good:
Widget build(BuildContext context) {
  useEffect(() {
    if (condition) {
      // ...
    }
  }, []);
}
Configuration: None.
DO name catch clause parameters consistently.
  • For catch-all: exception should be named err and stacktrace st
  • For typed catch: stacktrace must be named st
Bad:
void f() {
  try {} catch (e, s) {}
}
Good:
void f() {
  try {} catch (err, st) {}
  try {} on SocketException catch (e, st) {}
}
Configuration:
LeanCodeLintConfig(
  catchParameterNames: CatchParameterNamesConfig(
    exception: 'error',
    stackTrace: 'stackTrace',
  ),
)
AVOID extending HookWidget if no hooks are used.Bad:
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    return Placeholder();
  }
}
Good:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Placeholder();
  }
}
Configuration: None.
DO prefix widget names with ‘Sliver’ if the widget returns slivers.Bad:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverToBoxAdapter();
  }
}
Good:
class SliverMyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverToBoxAdapter();
  }
}
Configuration:
LeanCodeLintConfig(applicationPrefix: 'Lncd')
This allows LncdSliverMyWidget as a valid name.
DO start comments/docs with an empty space.Bad:
//some comment
///some doc
Good:
// some comment
/// some doc
Configuration: None.
AVOID using items disallowed by the design system.This rule highlights forbidden usages and suggests alternatives preferred by the design system.Configuration:
LeanCodeLintConfig(
  designSystemItemReplacements: {
    'LncdText': [
      DesignSystemForbiddenItem(name: 'Text', packageName: 'flutter'),
      DesignSystemForbiddenItem(name: 'RichText', packageName: 'flutter'),
    ],
    'LncdScaffold': [
      DesignSystemForbiddenItem(name: 'Scaffold', packageName: 'flutter'),
    ],
  },
)
AVOID using Column, Row, Flex, Wrap, SliverList, MultiSliver, etc. with a single child.Bad:
Widget build(BuildContext context) {
  return Column(
    children: [Container()],
  );
}
Good:
Widget build(BuildContext context) {
  return Container();
}
Configuration: None.
AVOID using MediaQuery.of or MediaQuery.maybeOf to access only one property.Dedicated methods offer better performance by minimizing unnecessary widget rebuilds.Bad:
final size = MediaQuery.of(context).size;
Good:
final size = MediaQuery.sizeOf(context);
Configuration: None.
DO use the Align widget instead of the Container widget with only the alignment parameter.Bad:
return Container(
  alignment: Alignment.bottomCenter,
  child: const SizedBox(),
);
Good:
return const Align(
  alignment: Alignment.bottomCenter,
  child: SizedBox(),
);
Configuration: None.
DO use Padding widget instead of the Container widget with only the margin parameter.Bad:
return Container(
  margin: EdgeInsets.all(10),
  child: const SizedBox(),
);
Good:
return const Padding(
  padding: EdgeInsets.all(10),
  child: SizedBox(),
);
Configuration: None.
DO use the Center widget instead of the Align widget with alignment set to Alignment.center.Bad:
return const Align(
  child: SizedBox(),
);
Good:
return const Center(
  child: SizedBox(),
);
Configuration: None.
DO mix in EquatableMixin instead of extending Equatable.Bad:
class Foobar extends Equatable {
  const Foobar(this.value);

  final int value;

  @override
  List<Object?> get props => [value];
}
Good:
class Foobar with EquatableMixin {
  const Foobar(this.value);

  final int value;

  @override
  List<Object?> get props => [value];
}
Configuration: None.

Assists

Assists are IDE refactorings that can be triggered by placing your cursor over relevant code and opening the code actions dialog (e.g., Ctrl+. or ⌘+. in VS Code).
Converts required positional parameters to named ones.Before:
class MyType {
  const MyType(
    this.pos0,
    this.hello, {
    this.named1,
  });

  final String pos0;
  final OtherType hello;
  final int? named1;
}
After applying to pos0:
class MyType {
  const MyType(
    this.hello, {
    required this.pos0,
    this.named1,
  });

  final String pos0;
  final OtherType hello;
  final int? named1;
}
Converts Dart records into named classes for better type safety and maintainability.
Converts iterable.map(...).toList() patterns into more readable collection-for syntax.

Disabling Rules

To disable a particular custom lint rule, set it to false in analysis_options.yaml:
plugins:
  leancode_lint:
    version: ^21.0.0
    diagnostics:
      prefix_widgets_returning_slivers: false

Additional Features

Comprehensive Linter Configuration

The package includes analysis_options.yaml with 200+ carefully selected and configured lint rules covering:
  • Strict type checking (strict-casts, strict-inference, strict-raw-types)
  • Flutter best practices
  • Performance optimizations
  • Code style consistency

Library Package Support

Use analysis_options_package.yaml for library projects to enable public API documentation requirements and other library-specific rules.

Package Information

This package requires Dart SDK >=3.11.0. Make sure your project meets this requirement before installation.

Build docs developers (and LLMs) love