unnecessary-lambdas
Same as unnecessary_lambdas in Dart lints
- Group
- Complexity
- Path
lint/complexity/unnecessary-lambdas- Default
- On (recommended)
- Origin
- Dart lints
- Rule
- 457 of 512
Flags a closure that only forwards its parameters to a function a tear-off could name.
names.forEach((name) { print(name); }) allocates a closure whose whole job
is to call print; names.forEach(print) says the same thing. The rule
fires on an anonymous, synchronous closure whose body is a single
invocation — => f(a, b), { f(a, b); }, or { return f(a, b); } —
passing exactly the closure's positional parameters in order and named
parameters under the same names, and on (a) => C(a) constructor calls a
C.new tear-off could replace. Like the analyzer, this does not compare
optional parameter defaults. The invoked function must stay the same object
between calls:
- a top-level, local, static, or instance method, or a
final, non-latevariable or field holding a function (the analyzer does not count aconstvariable, field, or enum value as final); - reached through a receiver that is a class name, an import prefix, or a
finalvariable or field (including immutable record fields), never throughthis, a parameter of the closure, or a null-aware access (a?.b). A member falcon cannot resolve on a stable receiver is taken to be a method, as the analyzer treats any member that is not a getter.
A function or method is itself a stable receiver, including p.call(x).
Optional constructor parameters do not prevent a tear-off from replacing
a required-parameter closure when the constructor accepts those arguments.
An optional closure parameter must stay optional on the target; legacy
@required annotations do not change that shape. Constructor invocations
with named arguments are excluded, matching the analyzer.
Optional positional super formals retain their optional shape even when
their inherited default value is unavailable.
Generic method invocations with explicit type arguments, calls through
deferred imports, and const constructor calls are skipped. A constructor
call only counts in an arrow body, and the constructor tear-off must fit the
closure's type, so every closure parameter's type must be known (written or
supplied by the context). When the closure is a named argument or a variable
initializer, a method tear-off must also fit the closure's type: the
parameter types must be known, and a block body that discards the call must
be in a void-returning context (otherwise the closure returns Null).
falcon stays silent when it cannot resolve the invoked function or receiver.
Constructor calls are only checked when constructor tear-offs are enabled
(language version 2.15 or later); before that, (a) => C(a) is silent.
Invalid
void log(Object? value) {}
class Point {
Point(this.x);
final int x;
}
class Logger {
void write(Object? value) {}
static void shared(Object? value) {}
}
class Screen {
final Logger logger = Logger();
final List<int> values = [];
void show(List<String> names) {
names.forEach((name) => logger.write(name));
names.forEach((name) => describe(name));
}
void describe(String name) {}
}
void main(List<String> names, List<int> numbers) {
names.forEach((name) {
print(name);
});
names.forEach((name) => log(name));
names.forEach((name) {
return Logger.shared(name);
});
final logger = Logger();
names.forEach((name) => logger.write(name));
final points = numbers.map((x) => Point(x));
final sink = <int>[];
numbers.forEach((n) => sink.add(n));
void local(int value) {}
numbers.forEach((n) => local(n));
print(points);
}
class Implicit {}
class Gen<T> {
Gen();
}
class Queue2 {
final List<int> items = [];
final StringBuffer buffer = StringBuffer();
void run(void Function() f) {}
void named({void Function()? onTap}) {}
void calls() {
run(() {
items.clear();
});
// A member falcon does not index on a stable receiver is a method.
run(() => buffer.clear());
// A block body discarding the call in a `void`-returning named context.
named(onTap: () {
items.clear();
});
}
}
void constructors() {
final implicit = () => Implicit();
final generic = () => Gen<int>();
final identity = () => Set<int>.identity();
print([implicit, generic, identity]);
}
// A `final` loop variable is stable even when its name is reused elsewhere.
void loops(List<Logger> loggers, List<String> names) {
for (final logger in loggers) {
names.forEach((name) => logger.write(name));
}
}
void reused(List<String> names) {
var logger = Logger();
logger = Logger();
names.forEach(logger.write);
}
// A list literal's element type is the closure's context.
List<Point Function(int)> builders() => [(x) => Point(x)];
void p(int value) {}
class OptionalConstructor {
OptionalConstructor([String value = '']);
}
// Matches StringBuffer's optional Object constructor without loading SDK sources.
class Buffer {
Buffer([Object content = '']);
}
void functionCallAndOptionalConstructors(List<int> numbers, List<String> names) {
numbers.forEach((value) => log.call(value));
numbers.forEach((value) => p.call(value));
names.map((value) => Buffer(value));
names.map((value) => Exception(value));
names.map((value) => OptionalConstructor(value));
names.map((String value) => OptionalConstructor(value));
} void show(List<String> names) { names.forEach((name) => logger.write(name)); names.forEach((name) => logger.write(name)); names.forEach((name) => describe(name));void main(List<String> names, List<int> numbers) { names.forEach((name) { }); names.forEach((name) => log(name)); names.forEach((name) => log(name)); names.forEach((name) { final logger = Logger(); names.forEach((name) => logger.write(name)); names.forEach((name) => logger.write(name)); final points = numbers.map((x) => Point(x)); final sink = <int>[]; numbers.forEach((n) => sink.add(n)); void local(int value) {} numbers.forEach((n) => local(n)); void calls() { run(() { // A member falcon does not index on a stable receiver is a method. run(() => buffer.clear()); // A block body discarding the call in a `void`-returning named context. named(onTap: () {void constructors() { final implicit = () => Implicit(); final implicit = () => Implicit(); final generic = () => Gen<int>(); final generic = () => Gen<int>(); final identity = () => Set<int>.identity(); for (final logger in loggers) { names.forEach((name) => logger.write(name));// A list literal's element type is the closure's context.List<Point Function(int)> builders() => [(x) => Point(x)];void functionCallAndOptionalConstructors(List<int> numbers, List<String> names) { numbers.forEach((value) => log.call(value)); numbers.forEach((value) => log.call(value)); numbers.forEach((value) => p.call(value)); numbers.forEach((value) => p.call(value)); names.map((value) => Buffer(value)); names.map((value) => Buffer(value)); names.map((value) => Exception(value)); names.map((value) => Exception(value)); names.map((value) => OptionalConstructor(value)); names.map((value) => OptionalConstructor(value)); names.map((String value) => OptionalConstructor(value));Valid
import 'dart:convert';
void log(Object? value) {}
void pair(Object? a, Object? b) {}
class Logger {
Logger();
const Logger.constant();
void write(Object? value) {}
}
class Screen {
Logger logger = Logger();
void show(List<String> names) {
names.forEach((name) => logger.write(name));
names.forEach((name) => this.write(name));
}
void write(String name) {}
}
Logger currentLogger() => Logger();
void main(List<String> names, List<List<String>> groups) async {
names.forEach((name) => log(name.length));
names.forEach((name) => pair(name, name));
names.forEach((name) {
log(name);
log(name);
});
names.forEach((name) async => log(name));
names.forEach((name) => currentLogger().write(name));
var logger = Logger();
names.forEach((name) => logger.write(name));
Logger? maybe = Logger();
names.forEach((name) => maybe?.write(name));
groups.forEach((group) => group.add('x'));
final callback = (name) => logger.write(name);
names.forEach(([Object? name]) => log(name));
print(callback);
}
class Box {
Box(this.name);
final String name;
static const Logger shared = Logger.constant();
}
enum Level {
info;
void write(Object? value) {}
}
const topLogger = Logger.constant();
class Recorder {
List<int> states = [];
Recorder(Stream<int> stream) {
// A non-final field is not a stable receiver.
stream.listen((state) {
states.add(state);
});
}
}
void contexts(List<String> names) {
// The closure's own type returns `Null`, which `log`'s tear-off does not fit.
final f = (String name) {
log(name);
};
// The analyzer does not count `const` variables and fields as final.
names.forEach((name) => Box.shared.write(name));
names.forEach((name) => topLogger.write(name));
const local = Logger.constant();
names.forEach((name) => local.write(name));
names.forEach((name) => Level.info.write(name));
// A block body is not checked for constructor calls.
names.map((name) {
return Box(name);
});
// A constructor tear-off must fit the closure's (dynamic) parameter type.
final g = (name) => Box(name);
print([f, g]);
}
class Lazy {
late final Logger logger = Logger();
// A `late` field is not stable.
void show(List<String> names) {
names.forEach((name) => logger.write(name));
}
}
// Imported SDK const values are not stable final receivers.
Future<dynamic> decodeInput(Future<String> input) =>
input.then((response) => json.decode(response));Configure unnecessary-lambdas
Set its severity in falcon.json. See the configuration guide.
{
"linter": {
"rules": {
"complexity": {
"unnecessary-lambdas": "error"
}
}
}
}Or silence one occurrence with a suppression comment; the reason is required.
// falcon-ignore lint/complexity/unnecessary-lambdas: <reason>More complexity rules
- unnecessary-overrides
unnecessary_overridesrecommendedFlags a member that does nothing but forward to
superwith the same arguments and resolved contract. - widget-tree-depthflutter
Limits the depth of an inline Flutter widget construction tree.
- avoid-classes-with-only-static-members
avoid_classes_with_only_static_membersFlags a class that declares only static members.
- avoid-collapsible-if
Flags an else-free
ifwhose only then-statement is another else-freeif. - avoid-complex-arithmetic-expressions
Reports arithmetic expressions containing too many terms.
- avoid-complex-conditions
Flags boolean expressions with more logical operands than the configured threshold.
- avoid-complex-loop-conditions
Flags loop conditions with more logical operands than the configured threshold.
- avoid-function-literals-in-foreach-calls
avoid_function_literals_in_foreach_callsFlags a function literal passed to an Iterable's
forEach.