Skip to content

Commit

Permalink
feat: defined_void_callback_type (#39)
Browse files Browse the repository at this point in the history
* feat: defined_void_callback_type

* docs: add docs for defined_void_callback_type

* test: add not lint test patterns

* docs: fix target sdk
  • Loading branch information
ronnnnn authored Oct 1, 2023
1 parent d0d37f5 commit 46e8eb2
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 0 deletions.
23 changes: 23 additions & 0 deletions packages/nilts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,35 @@ Some of lint rules support quick fixes on IDE.

| Rule name | Overview | Target SDK | Rule type | Maturity level | Quick fix |
| :-- | :-- | :--: | :--: | :--: | :--: |
| [defined\_void\_callback\_type](#defined_void_callback_type) | Checks `void Function()` definitions. | Any versions nilts supports | Practice | Experimental | ✅️ |
| [fixed\_text\_scale\_factor\_rich\_text](#fixed_text_scale_factor_rich_text) | Checks usage of `textScaleFactor` in `RichText` constructor. | Any versions nilts supports | Practice | Experimental | ✅️ |
| [flaky\_tests\_with\_set\_up\_all](#flaky_tests_with_set_up_all) | Checks `setUpAll` usages. | Any versions nilts supports | Practice | Experimental | ✅️ |
| [unnecessary\_rebuilds\_from\_media\_query](#unnecessary_rebuilds_from_media_query) | Checks `MediaQuery.xxxOf(context)` or `MediaQuery.maybeXxxOf(context)` usages. | >= Flutter 3.10.0 (Dart 3.0.0) | Practice | Experimental | ✅️ |

### Details

#### defined_void_callback_type

- Target SDK: Any versions nilts supports
- Rule type: Practice
- Maturity level: Experimental
- Quick fix:

**Consider** replace `void Function()` with `VoidCallback` which is defined in Flutter SDK.

**BAD:**

```dart
final void Function() callback;
```


**GOOD:**

```dart
final VoidCallback callback;
```

#### fixed_text_scale_factor_rich_text

- Target SDK: Any versions nilts supports
Expand Down
2 changes: 2 additions & 0 deletions packages/nilts/lib/nilts.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:nilts/src/lints/defined_void_callback_type.dart';
import 'package:nilts/src/lints/fixed_text_scale_factor_rich_text.dart';
import 'package:nilts/src/lints/flaky_tests_with_set_up_all.dart';
import 'package:nilts/src/lints/unnecessary_rebuilds_from_media_query.dart';
Expand All @@ -11,6 +12,7 @@ PluginBase createPlugin() => _NiltsLint();
class _NiltsLint extends PluginBase {
@override
List<LintRule> getLintRules(CustomLintConfigs configs) => [
const DefinedVoidCallbackType(),
const FixedTextScaleFactorRichText(),
const FlakyTestsWithSetUpAll(),
const UnnecessaryRebuildsFromMediaQuery(),
Expand Down
3 changes: 3 additions & 0 deletions packages/nilts/lib/src/change_priority.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class ChangePriority {
/// The priority for [_ReplaceWithSetUp].
static const int replaceWithSetUp = 100;

/// The priority for [_ReplaceWithVoidCallback].
static const int replaceWithVoidCallback = 100;

/// The priority for [_UnwrapSetUpAll].
static const int unwrapSetUpAll = 90;
}
92 changes: 92 additions & 0 deletions packages/nilts/lib/src/lints/defined_void_callback_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:nilts/src/change_priority.dart';

/// A class for `defined_void_callback_type` rule.
///
/// This rule checks defining `void Function()` type.
///
/// - Target SDK: Any versions nilts supports
/// - Rule type: Practice
/// - Maturity level: Experimental
/// - Quick fix: ✅
///
/// **Consider** replace `void Function()` with `VoidCallback` which is defined
/// in Flutter SDK.
///
/// **BAD:**
/// ```dart
/// final void Function() callback;
/// ```
///
/// **GOOD:**
/// ```dart
/// final VoidCallback callback;
/// ```
class DefinedVoidCallbackType extends DartLintRule {
/// Create a new instance of [DefinedVoidCallbackType].
const DefinedVoidCallbackType() : super(code: _code);

static const _code = LintCode(
name: 'defined_void_callback_type',
problemMessage: 'VoidCallback type is defined in Flutter SDK.',
url: 'https://github.com/ronnnnn/nilts#defined_void_callback_type',
);

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addTypeAnnotation((node) {
final type = node.type;
// Do nothing if the type is not Function.
if (type is! FunctionType) return;

// Do nothing if Function has parameters.
if (type.parameters.isNotEmpty) return;

// Do nothing if the return type is not void.
final returnType = type.returnType;
if (returnType is! VoidType) return;

reporter.reportErrorForNode(_code, node);
});
}

@override
List<Fix> getFixes() => [
_ReplaceWithVoidCallbackType(),
];
}

class _ReplaceWithVoidCallbackType extends DartFix {
@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
AnalysisError analysisError,
List<AnalysisError> others,
) {
context.registry.addTypeAnnotation((node) {
if (!node.sourceRange.intersects(analysisError.sourceRange)) return;

reporter
.createChangeBuilder(
message: 'Replace with VoidCallback',
priority: ChangePriority.replaceWithVoidCallback,
)
.addDartFileEdit((builder) {
final delta = node.question != null ? -1 : 0;
builder.addSimpleReplacement(
node.sourceRange.getMoveEnd(delta),
'VoidCallback',
);
});
});
}
}
79 changes: 79 additions & 0 deletions packages/nilts_test/test/lints/defined_void_callback_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// ignore_for_file: prefer_function_declarations_over_variables
// ignore_for_file: type_init_formals
// ignore_for_file: unused_element

import 'package:flutter/material.dart';

void main() {
runApp(const MainApp());
}

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

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MainButton(() {}),
),
),
);
}
}

class MainButton extends StatelessWidget {
const MainButton(
// expect_lint: defined_void_callback_type
void Function() this.onPressed, {
// expect_lint: defined_void_callback_type
void Function()? this.onNullablePressed,
void Function(int)? this.onParamPressed,
int Function()? this.onNotVoidPressed,
super.key,
});

// expect_lint: defined_void_callback_type
final void Function() onPressed;
// expect_lint: defined_void_callback_type
final void Function()? onNullablePressed;
final void Function(int)? onParamPressed;
final int Function()? onNotVoidPressed;

void _onPressed(
// expect_lint: defined_void_callback_type
void Function() onPressed, {
// expect_lint: defined_void_callback_type
void Function()? onNullablePressed,
void Function(int)? onParamPressed,
int Function()? onNotVoidPressed,
}) {}

@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: () {
_onPressed(() {});
onPressed();
},
child: const Text('Hello World!'),
);
}
}

// expect_lint: defined_void_callback_type
final void Function() globalFunction = () {};
// expect_lint: defined_void_callback_type
const void Function()? globalNullableFunction = null;
const void Function(int)? globalParamFunction = null;
const int Function()? globalNotVoidFunction = null;

void _globalFunction(
// expect_lint: defined_void_callback_type
void Function() onPressed, {
// expect_lint: defined_void_callback_type
void Function()? onNullablePressed,
void Function(int)? onParamPressed,
int Function()? onNotVoidPressed,
}) {}

0 comments on commit 46e8eb2

Please sign in to comment.