flutter/dev/bots/custom_rules/no_double_clamp.dart
Michael Goderbauer 5491c8c146
Auto-format Framework (#160545)
This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.

**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
2024-12-19 20:06:21 +00:00

116 lines
3.7 KiB
Dart

// Copyright 2014 The Flutter Authors. 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/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import '../utils.dart';
import 'analyze.dart';
/// Verify that we use clampDouble instead of double.clamp for performance
/// reasons.
///
/// See also:
/// * https://github.com/flutter/flutter/pull/103559
/// * https://github.com/flutter/flutter/issues/103917
final AnalyzeRule noDoubleClamp = _NoDoubleClamp();
class _NoDoubleClamp implements AnalyzeRule {
final Map<ResolvedUnitResult, List<AstNode>> _errors = <ResolvedUnitResult, List<AstNode>>{};
@override
void applyTo(ResolvedUnitResult unit) {
final _DoubleClampVisitor visitor = _DoubleClampVisitor();
unit.unit.visitChildren(visitor);
final List<AstNode> violationsInUnit = visitor.clampAccessNodes;
if (violationsInUnit.isNotEmpty) {
_errors.putIfAbsent(unit, () => <AstNode>[]).addAll(violationsInUnit);
}
}
@override
void reportViolations(String workingDirectory) {
if (_errors.isEmpty) {
return;
}
foundError(<String>[
for (final MapEntry<ResolvedUnitResult, List<AstNode>> entry in _errors.entries)
for (final AstNode node in entry.value)
'${locationInFile(entry.key, node, workingDirectory)}: ${node.parent}',
'\n${bold}For performance reasons, we use a custom "clampDouble" function instead of using "double.clamp".$reset',
]);
}
@override
String toString() => 'No "double.clamp"';
}
class _DoubleClampVisitor extends RecursiveAstVisitor<void> {
final List<AstNode> clampAccessNodes = <AstNode>[];
// We don't care about directives or comments.
@override
void visitImportDirective(ImportDirective node) {}
@override
void visitExportDirective(ExportDirective node) {}
@override
void visitComment(Comment node) {}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.name != 'clamp' || node.staticElement is! MethodElement) {
return;
}
final bool isAllowed = switch (node.parent) {
// PropertyAccess matches num.clamp in tear-off form. Always prefer
// doubleClamp over tear-offs: even when all 3 operands are int literals,
// the return type doesn't get promoted to int:
// final x = 1.clamp(0, 2); // The inferred return type is int, where as:
// final f = 1.clamp;
// final y = f(0, 2) // The inferred return type is num.
PropertyAccess(
target: Expression(
staticType: DartType(isDartCoreDouble: true) ||
DartType(isDartCoreNum: true) ||
DartType(isDartCoreInt: true),
),
) =>
false,
// Expressions like `final int x = 1.clamp(0, 2);` should be allowed.
MethodInvocation(
target: Expression(staticType: DartType(isDartCoreInt: true)),
argumentList: ArgumentList(
arguments: [
Expression(staticType: DartType(isDartCoreInt: true)),
Expression(staticType: DartType(isDartCoreInt: true)),
],
),
) =>
true,
// Otherwise, disallow num.clamp() invocations.
MethodInvocation(
target: Expression(
staticType: DartType(isDartCoreDouble: true) ||
DartType(isDartCoreNum: true) ||
DartType(isDartCoreInt: true),
),
) =>
false,
_ => true,
};
if (!isAllowed) {
clampAccessNodes.add(node);
}
}
}