Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: defined_void_callback_type #39

Merged
merged 4 commits into from
Oct 1, 2023
Merged
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
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,
}) {}