mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Extacted from #130101, dropped the `@_debugAssert` stuff from that PR so it's easier to review.
105 lines
3.7 KiB
Dart
105 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 'package:path/path.dart' as path;
|
|
|
|
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;
|
|
}
|
|
|
|
String locationInFile(ResolvedUnitResult unit, AstNode node) {
|
|
return '${path.relative(path.relative(unit.path, from: workingDirectory))}:${unit.lineInfo.getLocation(node.offset).lineNumber}';
|
|
}
|
|
|
|
foundError(<String>[
|
|
for (final MapEntry<ResolvedUnitResult, List<AstNode>> entry in _errors.entries)
|
|
for (final AstNode node in entry.value)
|
|
'${locationInFile(entry.key, node)}: ${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);
|
|
}
|
|
}
|
|
}
|