leancode_lint
An opinionated set of high-quality, robust, and up-to-date lint rules used at LeanCode.
Installation
- 
Add leancode_lintas a dev dependency in your project'spubspec.yaml.dart pub add leancode_lint --dev
- 
In your analysis_options.yamladdinclude: package:leancode_lint/analysis_options.yaml. You might want to exclude some files (e.g generated json serializable) from analysis.
- 
Enable the leancode_lintanalyzer plugin inanalysis_options.yaml. You can customize lint rules by adding aleancode_lintconfig.
- 
Run flutter pub getin your project main directory and restart your IDE. You're ready to go!
Example analysis_options.yaml:
include: package:leancode_lint/analysis_options.yaml
plugins:
  leancode_lint: ^20.0.0-dev.3
# Optional lint rules configuration
leancode_lint:
  application_prefix: Lncd
  use_design_system_item:
    AppText:
      - instead_of: Text
        from_package: flutter
      - instead_of: RichText
        from_package: flutter
    AppScaffold:
      - instead_of: Scaffold
        from_package: flutter
analyzer:
  exclude:
    - '**/*.g.dart'
Usage in libraries
If your package is a library rather than a binary application, you will expose some public API for users of your library. Therefore, you should use lint rules optimized for this case by just changing your include entry in analysis_options.yaml:
include: package:leancode_lint/analysis_options_package.yaml
Custom lint rules
To disable a particular custom lint rule, set the rule to false in analysis_options.yaml. For example, to disable prefix_widgets_returning_slivers:
plugins:
  leancode_lint: ^20.0.0-dev.3
  diagnostics:
    prefix_widgets_returning_slivers: false
add_cubit_suffix_for_your_cubits
DO add a 'Cubit' suffix to your cubit names.
BAD:
class MyClass extends Cubit<int> {}
GOOD:
class MyClassCubit extends Cubit<int> {}
Configuration
None.
avoid_conditional_hooks
AVOID using hooks conditionally
BAD:
Widget build(BuildContext context) {
  if (condition) {
    useEffect(() {
      // ...
    }, []);
  }
}
BAD:
Widget build(BuildContext context) {
  final controller = this.controller ?? useTextEditingController();
}
BAD:
Widget build(BuildContext context) {
  if (condition) {
    return Text('Early return');
  }
  final state = useState(0);
}
GOOD:
Widget build(BuildContext context) {
  useEffect(() {
    if (condition) {
        // ...
    }
  }, []);
}
GOOD:
Widget build(BuildContext context) {
  final backingController = useTextEditingController();
  final controller = this.controller ?? backingController;
}
GOOD:
Widget build(BuildContext context) {
  final state = useState(0);
  if (condition) {
    return Text('Early return');
  }
}
Configuration
None.
catch_parameter_names
DO name catch clause parameters consistently
- if it's a catch-all, the exception should be named errand the stacktracest
- if it's a typed catch, the stacktrace has to be named st
BAD:
try {} catch (e, s) {}
try {} on SocketException catch (e, s) {}
GOOD:
try {} catch (err, st) {}
try {} on SocketException catch (e, st) {}
Configuration
None.
hook_widget_does_not_use_hooks
AVOID extending HookWidget if no hooks are used.
BAD:
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    return Placeholder();
  }
}
BAD:
HookBuilder(
  builder: (context) {
    return Placeholder();
  },
);
GOOD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Placeholder();
  }
}
GOOD:
Builder(
  builder: (context) {
    return Placeholder();
  },
);
Configuration
None.
prefix_widgets_returning_slivers
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
- application_prefix: A string. Specifies the application prefix to accept sliver prefixes. For example if set to "Lncd" then "LncdSliverMyWidget" is a valid sliver name.
leancode_lint:
  application_prefix: Lncd
start_comments_with_space
DO start comments/docs with an empty space.
BAD:
//some comment
///some doc
GOOD:
// some comment
/// some doc
Configuration
None.
use_design_system_item
AVOID using items disallowed by the design system.
This rule has to be configured to do anything. The rule will highlight forbidden usages and suggest alternatives preferred by the design system.
Configuration
- <preferredItem>: The item to be preferred. A list of objects:- instead_of: A required string. Name of the item that is forbidden.
- from_package: A required string. Name of the package from which that forbidden item is from.
 
leancode_lint:
  use_design_system_item:
    LncdText:
      - instead_of: Text
        from_package: flutter
      - instead_of: RichText
        from_package: flutter
    LncdScaffold:
      - instead_of: Scaffold
        from_package: flutter
avoid_single_child_in_multi_child_widgets
AVOID using Column, Row, Flex, Wrap, SliverList, MultiSliver, SliverChildListDelegate, SliverMainAxisGroup, and SliverCrossAxisGroup with a single child.
BAD:
Column(
  children: [
    Container(),
  ]
)
GOOD:
Container(),
Configuration
None.
use_dedicated_media_query_methods
AVOID using MediaQuery.of or MediaQuery.maybeOf to access only one property. Instead, prefer dedicated methods, for example MediaQuery.paddingOf(context).
Dedicated methods offer better performance by minimizing unnecessary widget rebuilds.
BAD:
MediaQuery.of(context).size
GOOD:
MediaQuery.sizeOf(context)
Configuration
None.
use_align
DO Use the Align widget instead of the Container widget with only the alignment parameter.
BAD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.bottomCenter,
      child: const SizedBox(),
    );
  }
}
GOOD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Align(
      alignment: Alignment.bottomCenter,
      child: SizedBox(),
    );
  }
}
Configuration
None
use_padding
DO Use Padding widget instead of the Container widget with only the margin parameter
BAD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10),
      child: const SizedBox(),
    );
  }
}
GOOD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Padding(
      padding: EdgeInsets.all(10),
      child: SizedBox(),
    );
  }
}
Configuration
None
prefer_center_over_align
DO Use the Center widget instead of the Align widget with the alignment parameter set to Alignment.center
BAD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Align(
      child: SizedBox(),
    );
  }
}
GOOD:
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: SizedBox(),
    );
  }
}
Configuration
None
Assists
Assists are IDE refactorings not related to a particular issue. They can be triggered by placing your cursor over a relevant piece of code and opening the code actions dialog. For instance, in VSCode this is done with ctrl+. or ⌘+..
See linked source code containing explanation in dart doc.
Libraries
- assists/convert_iterable_map_to_collection_for
- assists/convert_positional_to_named_formal
- assists/convert_record_into_nominal_type
- config
- helpers
- lints/add_cubit_suffix_for_cubits
- lints/avoid_conditional_hooks
- lints/avoid_single_child_in_multi_child_widget
- lints/catch_parameter_names
- lints/constructor_parameters_and_fields_should_have_the_same_order
- lints/hook_widget_does_not_use_hooks
- lints/prefer_center_over_align
- lints/prefix_widgets_returning_slivers
- lints/start_comments_with_space
- lints/use_align
- lints/use_dedicated_media_query_methods
- lints/use_design_system_item
- lints/use_instead_type
- lints/use_padding
- main
- type_checker
- utils