Skip to content

Commit

Permalink
Merge branch 'main' into add-avoid-sliver-to-box-adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
naipaka authored Jul 29, 2024
2 parents 0475751 + 4cfce85 commit 35f7d4d
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 1 deletion.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"ints",
"pubspec",
"redeclares",
"RGBO",
"subosito",
"Supertypes",
"tearoffs",
Expand Down
6 changes: 5 additions & 1 deletion packages/altive_lints/lib/altive_lints.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import 'package:custom_lint_builder/custom_lint_builder.dart';

import 'src/lints/avoid_consecutive_sliver_to_box_adapter.dart';
import 'src/lints/avoid_hardcoded_color.dart';
import 'src/lints/avoid_hardcoded_japanese.dart';
import 'src/lints/avoid_shrink_wrap_in_list_view.dart';
import 'src/lints/avoid_single_child.dart';

PluginBase createPlugin() => _AltivePlugin();

class _AltivePlugin extends PluginBase {
@override
List<LintRule> getLintRules(CustomLintConfigs configs) => [
const AvoidConsecutiveSliverToBoxAdapter(),
const AvoidHardcodedColor(),
const AvoidHardcodedJapanese(),
const AvoidShrinkWrapInListView(),
const AvoidConsecutiveSliverToBoxAdapter(),
const AvoidSingleChild(),
];
}
78 changes: 78 additions & 0 deletions packages/altive_lints/lib/src/lints/avoid_hardcoded_color.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

/// An `avoid_hardcoded_color` rule that discourages the use of
/// hardcoded colors directly in the code, promoting the use of `ColorScheme`,
/// `ThemeExtension`, or other Theme-based systems for defining colors.
///
/// This practice ensures that colors are consistent and
/// adaptable to different themes and accessibility settings.
///
/// ### Example
///
/// #### BAD:
///
/// ```dart
/// Container(
/// color: Color(0xFF00FF00), // LINT
/// );
/// ```
///
/// #### GOOD:
///
/// ```dart
/// Container(
/// color: Theme.of(context).colorScheme.primary,
/// );
/// ```
class AvoidHardcodedColor extends DartLintRule {
const AvoidHardcodedColor() : super(code: _lintCode);

static const _lintCode = LintCode(
name: 'avoid_hardcoded_color',
problemMessage: 'Avoid using hardcoded color. Use ColorScheme, '
'ThemeExtension, or other Theme-based color definitions instead. '
'Consider using Theme.of(context).colorScheme or a custom '
'ThemeExtension for color definitions.',
);

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addInstanceCreationExpression((node) {
final typeName =
node.staticType?.getDisplayString(withNullability: false);

if (typeName == 'Color') {
reporter.reportErrorForNode(code, node);
}
});

context.registry.addPrefixedIdentifier((node) {
final prefix = node.prefix.name;
if (prefix == 'Colors') {
final element = node.staticElement;
if (element is PropertyAccessorElement) {
final returnType = element.returnType;
if (_isColorType(returnType)) {
reporter.reportErrorForNode(code, node);
}
}
}
});
}

bool _isColorType(DartType? type) {
return type != null &&
(type.isDartCoreInt ||
type.getDisplayString(withNullability: false) == 'Color' ||
type.getDisplayString(withNullability: false) == 'MaterialColor' ||
type.getDisplayString(withNullability: false) ==
'MaterialAccentColor');
}
}
77 changes: 77 additions & 0 deletions packages/altive_lints/lib/src/lints/avoid_single_child.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:collection/collection.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

/// An `avoid_single_child` rule that warns against using layout
/// widgets intended for multiple children with only one child.
///
/// This includes widgets like `Column`,`Row`, `Flex`, `Wrap`,
/// `ListView`, and `SliverList`.
///
/// Using these widgets with a single child can lead to
/// unnecessary overhead and less efficient layouts.
/// Instead, consider using a widget more suited for single
/// children or adding more children to the widget.
///
/// ### Example
///
/// #### BAD:
///
/// ```dart
/// Column(
/// children: <Widget>[YourWidget()], // LINT
/// );
/// ```
///
/// #### GOOD:
///
/// ```dart
/// Center(child: YourWidget());
/// // or
/// Column(
/// children: <Widget>[YourWidget1(), YourWidget2()],
/// );
/// ```
class AvoidSingleChild extends DartLintRule {
const AvoidSingleChild() : super(code: _code);

static const _code = LintCode(
name: 'avoid_single_child',
problemMessage:
'Avoid using a single child in widgets that expect multiple children. '
'Consider using a single child widget or adding more children.',
);

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addInstanceCreationExpression((node) {
final className =
node.staticType?.getDisplayString(withNullability: false);
if ([
'Column',
'Row',
'Flex',
'Wrap',
'ListView',
'SliverList',
].contains(className)) {
final childrenArg = node.argumentList.arguments.firstWhereOrNull(
(arg) => arg is NamedExpression && arg.name.label.name == 'children',
);

final childrenList = childrenArg is NamedExpression
? childrenArg.expression as ListLiteral
: null;

if (childrenList != null && childrenList.elements.length == 1) {
reporter.reportErrorForNode(_code, node);
}
}
});
}
}
20 changes: 20 additions & 0 deletions packages/altive_lints/lint_test/lints/avoid_hardcoded_color.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
const MyWidget({super.key});

@override
Widget build(BuildContext context) {
return Column(
children: [
// expect_lint: avoid_hardcoded_color
const ColoredBox(color: Color(0xFF00FF00)),
// expect_lint: avoid_hardcoded_color
const ColoredBox(color: Color.fromRGBO(0, 255, 0, 1)),
// expect_lint: avoid_hardcoded_color
const ColoredBox(color: Colors.green),
ColoredBox(color: Theme.of(context).colorScheme.primary),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore_for_file: avoid_single_child
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
Expand Down
50 changes: 50 additions & 0 deletions packages/altive_lints/lint_test/lints/avoid_single_child.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
const MyWidget({super.key});

@override
Widget build(BuildContext context) {
return ListView(
children: [
// expect_lint: avoid_single_child
const Column(
children: [
Text('Hello'),
],
),
// expect_lint: avoid_single_child
const Row(
children: [
Text('World'),
],
),
// expect_lint: avoid_single_child
const Flex(
direction: Axis.horizontal,
children: [
Text('Hello'),
],
),
// expect_lint: avoid_single_child
const Wrap(
children: [
Text('World'),
],
),
// expect_lint: avoid_single_child
ListView(
children: const [
Text('Hello'),
],
),
// expect_lint: avoid_single_child
SliverList.list(
children: const [
Text('World'),
],
),
],
);
}
}

0 comments on commit 35f7d4d

Please sign in to comment.