From 3533db198534878d54d6e17399b61bad2b9cee7f Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 27 Oct 2022 11:54:20 -0700 Subject: [PATCH] Add lint implicit_call_tearoffs (#3592) In an effort to simplify the language we will consider removing the implicit `call` tearoff which can coerce an instance of any class which defines a `call` method into a `Function` type. Authors should explicitly add the `.call` so there is less magic. --- CHANGELOG.md | 4 + example/all.yaml | 1 + lib/src/rules.dart | 2 + lib/src/rules/implicit_call_tearoffs.dart | 72 ++++++++++++ lib/src/version.dart | 2 +- pubspec.yaml | 2 +- test_data/rules/implicit_call_tearoffs.dart | 123 ++++++++++++++++++++ 7 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 lib/src/rules/implicit_call_tearoffs.dart create mode 100644 test_data/rules/implicit_call_tearoffs.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 113720a47..ed1298fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.29.0-dev + +- new lint: `implicit_call_tearoffs` + # 1.28.0 - update `avoid_redundant_argument_values` to work with enum declarations diff --git a/example/all.yaml b/example/all.yaml index 1238f235b..9de26ace3 100644 --- a/example/all.yaml +++ b/example/all.yaml @@ -78,6 +78,7 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports + - implicit_call_tearoffs - iterable_contains_unrelated_type - join_return_with_assignment - leading_newlines_in_multiline_strings diff --git a/lib/src/rules.dart b/lib/src/rules.dart index f127bd3cf..dfa824042 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -80,6 +80,7 @@ import 'rules/file_names.dart'; import 'rules/flutter_style_todos.dart'; import 'rules/hash_and_equals.dart'; import 'rules/implementation_imports.dart'; +import 'rules/implicit_call_tearoffs.dart'; import 'rules/invariant_booleans.dart'; import 'rules/iterable_contains_unrelated_type.dart'; import 'rules/join_return_with_assignment.dart'; @@ -297,6 +298,7 @@ void registerLintRules({bool inTestMode = false}) { ..register(FlutterStyleTodos()) ..register(HashAndEquals()) ..register(ImplementationImports()) + ..register(ImplicitCallTearoffs()) ..register(InvariantBooleans()) ..register(IterableContainsUnrelatedType()) ..register(JoinReturnWithAssignment()) diff --git a/lib/src/rules/implicit_call_tearoffs.dart b/lib/src/rules/implicit_call_tearoffs.dart new file mode 100644 index 000000000..747f81a8d --- /dev/null +++ b/lib/src/rules/implicit_call_tearoffs.dart @@ -0,0 +1,72 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +import '../analyzer.dart'; + +const _desc = + r'Explicitly tear-off `call` methods when using an object as a Function.'; + +const _details = r''' +**DO** +Explicitly tear off `.call` methods from objects when assigning to a Function +type. There is less magic with an explicit tear off. Future language versions +may remove the implicit call tear off. + +**BAD:** +```dart +class Callable { + void call() {} +} +void callIt(void Function() f) { + f(); +} + +callIt(Callable()); +``` + +**GOOD:** +```dart +class Callable { + void call() {} +} +void callIt(void Function() f) { + f(); +} + +callIt(Callable().call); +``` + +'''; + +class ImplicitCallTearoffs extends LintRule { + ImplicitCallTearoffs() + : super( + name: 'implicit_call_tearoffs', + description: _desc, + details: _details, + group: Group.style, + maturity: Maturity.experimental, + ); + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this); + registry.addImplicitCallReference(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitImplicitCallReference(ImplicitCallReference node) { + rule.reportLint(node); + } +} diff --git a/lib/src/version.dart b/lib/src/version.dart index 302afa1e2..a56364869 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -3,4 +3,4 @@ // BSD-style license that can be found in the LICENSE file. /// Package version. Synchronized w/ pubspec.yaml. -const String version = '1.28.0'; +const String version = '1.29.0-dev'; diff --git a/pubspec.yaml b/pubspec.yaml index 7b1906488..fbf0bb452 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: linter -version: 1.28.0 +version: 1.29.0-dev description: >- The implementation of the lint rules supported by the analyzer framework. diff --git a/test_data/rules/implicit_call_tearoffs.dart b/test_data/rules/implicit_call_tearoffs.dart new file mode 100644 index 000000000..663f3ae52 --- /dev/null +++ b/test_data/rules/implicit_call_tearoffs.dart @@ -0,0 +1,123 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// test w/ `dart test -N implicit_call_tearoffs` + +class C { + void call() {} + void other() {} +} + +class C2 { + void call(T arg) {} +} + +void callIt(void Function() f) { + f(); +} + +void callIt2(void Function(int) f) { + f(0); +} + +void Function() r1() => C(); // LINT +void Function() r2() => C().call; // OK +void Function() r3(C c) => c; // LINT +void Function() r4(C c) => c.call; // OK + +void Function() r5(C? c1, C c2) { + return c1 ?? c2; // LINT +} + +void Function() r6(C? c1, C c2) { + return c1?.call ?? c2.call; // OK +} + +void Function() r7() { + return C()..other(); // LINT +} + +void Function() r8() { + return (C()..other()).call; // OK +} + +List r9(C c) { + return [c]; // LINT +} + +List r10(C c) { + return [c.call]; // OK +} + +void Function(int) r11(C2 c) => c; // LINT +void Function(int) r12(C2 c) => c.call; // OK + +void main() { + callIt(C()); // LINT + callIt(C().call); // OK + Function f1 = C(); // LINT + Function f2 = C().call; // OK + void Function() f3 = C(); // LINT + void Function() f4 = C().call; // OK + + final c = C(); + callIt(c); // LINT + callIt(c.call); // OK + Function f5 = c; // LINT + Function f6 = c.call; // OK + void Function() f7 = c; // LINT + void Function() f8 = c.call; // OK + + [ + C(), // LINT + C().call, //OK + c, // LINT + c.call, // OK + ]; + + callIt2(C2()); // LINT + callIt2(C2().call); // OK + callIt2(C2()); // LINT + callIt2(C2().call); // OK + Function f9 = C2(); // LINT + Function f10 = C2().call; // OK + Function f11 = C2(); // LINT + Function f12 = C2().call; // OK + void Function(T) f13 = C2(); // LINT + void Function(T) f14 = C2().call; // OK + void Function(int) f15 = C2(); // LINT + void Function(int) f16 = C2().call; // OK + void Function(int) f17 = C2(); // LINT + void Function(int) f18 = C2().call; // OK + + final c2 = C2(); + callIt2(c2); // LINT + callIt2(c2.call); // OK + callIt2(c2); // LINT + callIt2(c2.call); // OK + Function f19 = c2; // LINT + Function f20 = c2.call; // OK + Function f21 = c2; // LINT + Function f22 = c2.call; // OK + void Function(T) f23 = c2; // LINT + void Function(T) f24 = c2.call; // OK + void Function(int) f25 = c2; // LINT + void Function(int) f26 = c2.call; // OK + void Function(int) f27 = c2; // LINT + void Function(int) f28 = c2.call; // OK + + [ + C2(), // LINT + C2().call, //OK + C2(), // LINT + C2().call, //OK + c2, // LINT + c2.call, // OK + c2, // LINT + c2.call, // OK + ]; + + C2(); // LINT + c2; // LINT +}