Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/leancode_lint/.fvmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"flutter": "3.38.1"
"flutter": "3.41.2"
}
54 changes: 53 additions & 1 deletion packages/leancode_lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ None.
</details>

<details>
<summary>`bloc_related_class_naming`</summary>
<summary><code>bloc_related_class_naming</code></summary>

### `bloc_related_class_naming`

Expand Down Expand Up @@ -392,6 +392,58 @@ None.

</details>

<details>
<summary><code>never_discard_build_context</code></summary>

### `never_discard_build_context`

**AVOID** discarding `BuildContext` parameters by naming them `_`.

Discarding a `BuildContext` causes the body to use an ancestor context instead,
risking incorrect theme lookups, navigation, or inherited widget reads.

**BAD:**

```dart
Widget myBuilder(BuildContext _) {
return const SizedBox();
}
```

**BAD:**

```dart
Widget build(BuildContext context) {
return Builder(
builder: (_) => const SizedBox(),
);
}
```

**GOOD:**

```dart
Widget myBuilder(BuildContext context) {
return const SizedBox();
}
```

**GOOD:**

```dart
Widget build(BuildContext context) {
return Builder(
builder: (context) => const SizedBox(),
);
}
```

#### Configuration

None.

</details>

<details>
<summary><code>prefix_widgets_returning_slivers</code></summary>

Expand Down
6 changes: 6 additions & 0 deletions packages/leancode_lint/lib/plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:leancode_lint/src/lints/bloc_related_class_naming.dart';
import 'package:leancode_lint/src/lints/catch_parameter_names.dart';
import 'package:leancode_lint/src/lints/constructor_parameters_and_fields_should_have_the_same_order.dart';
import 'package:leancode_lint/src/lints/hook_widget_does_not_use_hooks.dart';
import 'package:leancode_lint/src/lints/never_discard_build_context.dart';
import 'package:leancode_lint/src/lints/prefer_equatable_mixin.dart';
import 'package:leancode_lint/src/lints/prefix_widgets_returning_slivers.dart';
import 'package:leancode_lint/src/lints/start_comments_with_space.dart';
Expand Down Expand Up @@ -63,6 +64,11 @@ final class LeanCodeLintPlugin extends Plugin {
HookWidgetDoesNotUseHooks.code,
ConvertHookWidgetToStatelessWidget.new,
)
..registerWarningRule(NeverDiscardBuildContext())
..registerFixForRule(
NeverDiscardBuildContext.code,
RenameDiscardedBuildContextFix.new,
)
// TODO: disabled by default until stabilized. Add documentation.
..registerLintRule(ConstructorParametersAndFieldsShouldHaveTheSameOrder())
..registerWarningRule(AvoidSingleChildInMultiChildWidgets())
Expand Down
2 changes: 1 addition & 1 deletion packages/leancode_lint/lib/src/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ abstract class ChangeWidgetNameFix extends ResolvedCorrectionProducer {
final String widgetName;

@override
FixKind? get fixKind => .new(
FixKind get fixKind => .new(
'leancode_lint.fix.replaceWith$widgetName',
DartFixKindPriority.standard,
'Replace with $widgetName',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class ConvertHookWidgetToStatelessWidget extends ResolvedCorrectionProducer {
ConvertHookWidgetToStatelessWidget({required super.context});

@override
FixKind? get fixKind => const FixKind(
FixKind get fixKind => const .new(
'leancode_lint.fix.convertHookWidgetToStatelessWidget',
DartFixKindPriority.standard,
'Convert HookWidget to StatelessWidget',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart';
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:leancode_lint/src/type_checker.dart';

/// Warns when a BuildContext parameter is explicitly discarded using `_`.
///
/// Discarding a context causes the body to use an ancestor context instead,
/// risking incorrect theme lookups, navigation, or inherited widget reads.
class NeverDiscardBuildContext extends AnalysisRule {
NeverDiscardBuildContext()
: super(name: code.lowerCaseName, description: code.problemMessage);

static const code = LintCode(
'never_discard_build_context',
"Don't discard BuildContext parameters.",
correctionMessage:
'Give the BuildContext parameter a name to avoid accidentally using an ancestor context.',
severity: .WARNING,
);

@override
LintCode get diagnosticCode => code;

@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
registry.addFormalParameterList(this, _Visitor(this));
}
}

class _Visitor extends SimpleAstVisitor<void> {
_Visitor(this.rule);

final AnalysisRule rule;

static const _buildContextChecker = TypeChecker.fromName(
'BuildContext',
packageName: 'flutter',
);

@override
void visitFormalParameterList(FormalParameterList node) {
node.parameters.forEach(_checkParameter);
}

void _checkParameter(FormalParameter parameter) {
final name = parameter.name;
if (name == null || name.lexeme != '_') {
return;
}

final type = parameter.declaredFragment?.element.type;
if (type == null) {
return;
}

if (_buildContextChecker.isExactlyType(type)) {
rule.reportAtToken(name);
}
}
}

class RenameDiscardedBuildContextFix extends ResolvedCorrectionProducer {
RenameDiscardedBuildContextFix({required super.context});

@override
FixKind get fixKind => const .new(
'leancode_lint.fix.renameDiscardedBuildContext',
DartFixKindPriority.standard,
"Rename to 'context'",
);

@override
CorrectionApplicability get applicability => .automatically;

@override
Future<void> compute(ChangeBuilder builder) async {
await builder.addDartFileEdit(
file,
(builder) => builder.addSimpleReplacement(
range.diagnostic(diagnostic!),
'context',
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class ConvertToEquatableMixin extends ResolvedCorrectionProducer {
ConvertToEquatableMixin({required super.context});

@override
FixKind? get fixKind => const FixKind(
FixKind get fixKind => const .new(
'leancode_lint.fix.convertToEquatableMixin',
DartFixKindPriority.standard,
'Convert to EquatableMixin',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class AddStartingSpaceToComment extends ResolvedCorrectionProducer {
AddStartingSpaceToComment({required super.context});

@override
FixKind? get fixKind => const .new(
FixKind get fixKind => const .new(
'leancode_lint.fix.addStartingSpaceToComment',
DartFixKindPriority.standard,
'Add leading space to comment',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class ReplaceMediaQueryOfWithDedicatedMethodFix
ReplaceMediaQueryOfWithDedicatedMethodFix({required super.context});

@override
FixKind? get fixKind => const .new(
FixKind get fixKind => const .new(
'leancode_lint.fix.replaceMediaQueryOfWithDedicatedMethod',
DartFixKindPriority.standard,
'Replace with the dedicated MediaQuery method',
Expand Down
2 changes: 1 addition & 1 deletion packages/leancode_lint/lib/src/lints/use_padding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class UsePaddingFix extends ResolvedCorrectionProducer {
UsePaddingFix({required super.context});

@override
FixKind? get fixKind => const .new(
FixKind get fixKind => const .new(
'leancode_lint.fix.usePadding',
DartFixKindPriority.standard,
'Replace with Padding',
Expand Down
Loading