From aa609127e760d6b02cc71d9fdcc3a2ca911f904a Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:25:26 -0800 Subject: [PATCH] Use dart analyze package for `num.clamp` (#139867) Extacted from #130101, dropped the `@_debugAssert` stuff from that PR so it's easier to review. --- dev/bots/analyze.dart | 82 +++----------- dev/bots/custom_rules/analyze.dart | 79 +++++++++++++ dev/bots/custom_rules/no_double_clamp.dart | 104 ++++++++++++++++++ dev/bots/pubspec.yaml | 2 +- .../root/packages/flutter/lib/bar.dart | 30 +++++ dev/bots/test/analyze_test.dart | 26 +++++ dev/bots/utils.dart | 4 +- .../cupertino/sliding_segmented_control.dart | 2 +- packages/flutter/lib/src/material/button.dart | 2 +- .../lib/src/material/button_style_button.dart | 2 +- .../flutter/lib/src/material/menu_anchor.dart | 4 +- .../src/material/paginated_data_table.dart | 2 +- packages/flutter/lib/src/material/tabs.dart | 4 +- .../flutter/lib/src/material/text_field.dart | 2 +- .../lib/src/painting/border_radius.dart | 8 +- .../flutter/lib/src/painting/box_border.dart | 16 +-- .../flutter/lib/src/painting/text_style.dart | 2 +- .../flutter/lib/src/rendering/paragraph.dart | 4 +- .../lib/src/services/text_formatter.dart | 2 +- .../flutter/lib/src/services/text_input.dart | 2 +- .../lib/src/widgets/implicit_animations.dart | 2 +- 21 files changed, 283 insertions(+), 98 deletions(-) create mode 100644 dev/bots/custom_rules/analyze.dart create mode 100644 dev/bots/custom_rules/no_double_clamp.dart diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 976a93d800..e43fdda7bc 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -17,6 +17,8 @@ import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'allowlist.dart'; +import 'custom_rules/analyze.dart'; +import 'custom_rules/no_double_clamp.dart'; import 'run_command.dart'; import 'utils.dart'; @@ -90,9 +92,6 @@ Future run(List arguments) async { printProgress('TargetPlatform tool/framework consistency'); await verifyTargetPlatform(flutterRoot); - printProgress('No Double.clamp'); - await verifyNoDoubleClamp(flutterRoot); - printProgress('All tool test files end in _test.dart...'); await verifyToolTestsEndInTestDart(flutterRoot); @@ -167,11 +166,23 @@ Future run(List arguments) async { // Analyze all the Dart code in the repo. printProgress('Dart analysis...'); - await _runFlutterAnalyze(flutterRoot, options: [ + final CommandResult dartAnalyzeResult = await _runFlutterAnalyze(flutterRoot, options: [ '--flutter-repo', ...arguments, ]); + if (dartAnalyzeResult.exitCode == 0) { + // Only run the private lints when the code is free of type errors. The + // lints are easier to write when they can assume, for example, there is no + // inheritance cycles. + final List rules = [noDoubleClamp]; + final String ruleNames = rules.map((AnalyzeRule rule) => '\n * $rule').join(); + printProgress('Analyzing code in the framework with the following rules:$ruleNames'); + await analyzeFrameworkWithRules(flutterRoot, rules); + } else { + printProgress('Skipped performing further analysis in the framework because "flutter analyze" finished with a non-zero exit code.'); + } + printProgress('Executable allowlist...'); await _checkForNewExecutables(); @@ -244,34 +255,6 @@ _Line _getLine(ParseStringResult parseResult, int offset) { return _Line(lineNumber, content); } -class _DoubleClampVisitor extends RecursiveAstVisitor { - _DoubleClampVisitor(this.parseResult); - - final List<_Line> clamps = <_Line>[]; - final ParseStringResult parseResult; - - @override - CompilationUnit? visitMethodInvocation(MethodInvocation node) { - final NodeList arguments = node.argumentList.arguments; - // This may produce false positives when `node.target` is not a subtype of - // num. The static type of `node.target` isn't guaranteed to be resolved at - // this time. Check whether the argument list consists of 2 positional args - // to reduce false positives. - final bool isNumClampInvocation = node.methodName.name == 'clamp' - && arguments.length == 2 - && !arguments.any((Expression exp) => exp is NamedExpression); - if (isNumClampInvocation) { - final _Line line = _getLine(parseResult, node.function.offset); - if (!line.content.contains('// ignore_clamp_double_lint')) { - clamps.add(line); - } - } - - node.visitChildren(this); - return null; - } -} - Future verifyTargetPlatform(String workingDirectory) async { final File framework = File('$workingDirectory/packages/flutter/lib/src/foundation/platform.dart'); final Set frameworkPlatforms = {}; @@ -350,41 +333,6 @@ Future verifyTargetPlatform(String workingDirectory) async { } } -/// Verify that we use clampDouble instead of Double.clamp for performance reasons. -/// -/// We currently can't distinguish valid uses of clamp from problematic ones so -/// if the clamp is operating on a type other than a `double` the -/// `// ignore_clamp_double_lint` comment must be added to the line where clamp is -/// invoked. -/// -/// See also: -/// * https://github.com/flutter/flutter/pull/103559 -/// * https://github.com/flutter/flutter/issues/103917 -Future verifyNoDoubleClamp(String workingDirectory) async { - final String flutterLibPath = '$workingDirectory/packages/flutter/lib'; - final Stream testFiles = - _allFiles(flutterLibPath, 'dart', minimumMatches: 100); - final List errors = []; - await for (final File file in testFiles) { - final ParseStringResult parseResult = parseFile( - featureSet: _parsingFeatureSet(), - path: file.absolute.path, - ); - final _DoubleClampVisitor visitor = _DoubleClampVisitor(parseResult); - visitor.visitCompilationUnit(parseResult.unit); - for (final _Line clamp in visitor.clamps) { - errors.add('${file.path}:${clamp.line}: `clamp` method used instead of `clampDouble`.'); - } - } - if (errors.isNotEmpty) { - foundError([ - ...errors, - '\n${bold}For performance reasons, we use a custom `clampDouble` function instead of using `Double.clamp`.$reset', - '\n${bold}For non-double uses of `clamp`, use `// ignore_clamp_double_lint` on the line to silence this message.$reset', - ]); - } -} - /// Verify Token Templates are mapped to correct file names while generating /// M3 defaults in /dev/tools/gen_defaults/bin/gen_defaults.dart. Future verifyTokenTemplatesUpdateCorrectFiles(String workingDirectory) async { diff --git a/dev/bots/custom_rules/analyze.dart b/dev/bots/custom_rules/analyze.dart new file mode 100644 index 0000000000..95a811956f --- /dev/null +++ b/dev/bots/custom_rules/analyze.dart @@ -0,0 +1,79 @@ +// 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 'dart:io' show Directory; + +import 'package:analyzer/dart/analysis/analysis_context.dart'; +import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/session.dart'; +import 'package:path/path.dart' as path; + +import '../utils.dart'; + +/// Analyzes the given `flutterRootDirectory` containing the flutter framework +/// source files, with the given [AnalyzeRule]s. +/// +/// If a compilation unit can not be resolved, this function ignores the +/// corresponding dart source file and logs an error using [foundError]. +Future analyzeFrameworkWithRules(String flutterRootDirectory, List rules) async { + final String flutterLibPath = path.canonicalize('$flutterRootDirectory/packages/flutter/lib'); + if (!Directory(flutterLibPath).existsSync()) { + foundError(['Analyzer error: the specified $flutterLibPath does not exist.']); + } + final AnalysisContextCollection collection = AnalysisContextCollection( + includedPaths: [flutterLibPath], + excludedPaths: [path.canonicalize('$flutterLibPath/fix_data')], + ); + + final List analyzerErrors = []; + for (final AnalysisContext context in collection.contexts) { + final Iterable analyzedFilePaths = context.contextRoot.analyzedFiles(); + final AnalysisSession session = context.currentSession; + + for (final String filePath in analyzedFilePaths) { + final SomeResolvedUnitResult unit = await session.getResolvedUnit(filePath); + if (unit is ResolvedUnitResult) { + for (final AnalyzeRule rule in rules) { + rule.applyTo(unit); + } + } else { + analyzerErrors.add('Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.'); + } + } + } + + if (analyzerErrors.isNotEmpty) { + foundError(analyzerErrors); + } + for (final AnalyzeRule verifier in rules) { + verifier.reportViolations(flutterRootDirectory); + } +} + +/// An interface that defines a set of best practices, and collects information +/// about code that violates the best practices in a [ResolvedUnitResult]. +/// +/// The [analyzeFrameworkWithRules] function scans and analyzes the specified +/// source directory using the dart analyzer package, and applies custom rules +/// defined in the form of this interface on each resulting [ResolvedUnitResult]. +/// The [reportViolations] method will be called at the end, once all +/// [ResolvedUnitResult]s are parsed. +/// +/// Implementers can assume each [ResolvedUnitResult] is valid compilable dart +/// code, as the caller only applies the custom rules once the code passes +/// `flutter analyze`. +abstract class AnalyzeRule { + /// Applies this rule to the given [ResolvedUnitResult] (typically a file), and + /// collects information about violations occurred in the compilation unit. + void applyTo(ResolvedUnitResult unit); + + /// Reports all violations in the resolved compilation units [applyTo] was + /// called on, if any. + /// + /// This method is called once all [ResolvedUnitResult] are parsed. + /// + /// The implementation typically calls [foundErrors] to report violations. + void reportViolations(String workingDirectory); +} diff --git a/dev/bots/custom_rules/no_double_clamp.dart b/dev/bots/custom_rules/no_double_clamp.dart new file mode 100644 index 0000000000..a9b5836f6d --- /dev/null +++ b/dev/bots/custom_rules/no_double_clamp.dart @@ -0,0 +1,104 @@ +// 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> _errors = >{}; + + @override + void applyTo(ResolvedUnitResult unit) { + final _DoubleClampVisitor visitor = _DoubleClampVisitor(); + unit.unit.visitChildren(visitor); + final List violationsInUnit = visitor.clampAccessNodes; + if (violationsInUnit.isNotEmpty) { + _errors.putIfAbsent(unit, () => []).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([ + for (final MapEntry> 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 { + final List clampAccessNodes = []; + + // 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); + } + } +} diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml index 2be63de606..5de0624826 100644 --- a/dev/bots/pubspec.yaml +++ b/dev/bots/pubspec.yaml @@ -5,6 +5,7 @@ environment: sdk: '>=3.2.0-0 <4.0.0' dependencies: + analyzer: 6.3.0 args: 2.4.2 crypto: 3.0.3 intl: 0.18.1 @@ -20,7 +21,6 @@ dependencies: _discoveryapis_commons: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" _fe_analyzer_shared: 65.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 6.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" archive: 3.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" diff --git a/dev/bots/test/analyze-test-input/root/packages/flutter/lib/bar.dart b/dev/bots/test/analyze-test-input/root/packages/flutter/lib/bar.dart index 2b14c6f4c2..af4bbd1b48 100644 --- a/dev/bots/test/analyze-test-input/root/packages/flutter/lib/bar.dart +++ b/dev/bots/test/analyze-test-input/root/packages/flutter/lib/bar.dart @@ -19,3 +19,33 @@ class Foo { /// Simply avoid this /// and simply do that. + +class ClassWithAClampMethod { + ClassWithAClampMethod clamp(double min, double max) => this; +} + +void testNoDoubleClamp(int input) { + final ClassWithAClampMethod nonDoubleClamp = ClassWithAClampMethod(); + // ignore: unnecessary_nullable_for_final_variable_declarations + final ClassWithAClampMethod? nonDoubleClamp2 = nonDoubleClamp; + // ignore: unnecessary_nullable_for_final_variable_declarations + final int? nullableInt = input; + final double? nullableDouble = nullableInt?.toDouble(); + + nonDoubleClamp.clamp(0, 2); + input.clamp(0, 2); + input.clamp(0.0, 2); // bad. + input.toDouble().clamp(0, 2); // bad. + + nonDoubleClamp2?.clamp(0, 2); + nullableInt?.clamp(0, 2); + nullableInt?.clamp(0, 2.0); // bad + nullableDouble?.clamp(0, 2); // bad. + + // ignore: unused_local_variable + final ClassWithAClampMethod Function(double, double)? tearOff1 = nonDoubleClamp2?.clamp; + // ignore: unused_local_variable + final num Function(num, num)? tearOff2 = nullableInt?.clamp; // bad. + // ignore: unused_local_variable + final num Function(num, num)? tearOff3 = nullableDouble?.clamp; // bad. +} diff --git a/dev/bots/test/analyze_test.dart b/dev/bots/test/analyze_test.dart index cf0c4fa114..dc0d8bca1c 100644 --- a/dev/bots/test/analyze_test.dart +++ b/dev/bots/test/analyze_test.dart @@ -7,6 +7,8 @@ import 'dart:io'; import 'package:path/path.dart' as path; import '../analyze.dart'; +import '../custom_rules/analyze.dart'; +import '../custom_rules/no_double_clamp.dart'; import '../utils.dart'; import 'common.dart'; @@ -226,4 +228,28 @@ void main() { expect(result, contains(':20')); expect(result, contains(':21')); }); + + test('analyze.dart - clampDouble', () async { + final String result = await capture(() => analyzeFrameworkWithRules( + testRootPath, + [noDoubleClamp], + ), shouldHaveErrors: true); + final String lines = [ + '║ packages/flutter/lib/bar.dart:37: input.clamp(0.0, 2)', + '║ packages/flutter/lib/bar.dart:38: input.toDouble().clamp(0, 2)', + '║ packages/flutter/lib/bar.dart:42: nullableInt?.clamp(0, 2.0)', + '║ packages/flutter/lib/bar.dart:43: nullableDouble?.clamp(0, 2)', + '║ packages/flutter/lib/bar.dart:48: nullableInt?.clamp', + '║ packages/flutter/lib/bar.dart:50: nullableDouble?.clamp', + ] + .map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')) + .join('\n'); + expect(result, + '╔═╡ERROR╞═══════════════════════════════════════════════════════════════════════\n' + '$lines\n' + '║ \n' + '║ For performance reasons, we use a custom "clampDouble" function instead of using "double.clamp".\n' + '╚═══════════════════════════════════════════════════════════════════════════════\n' + ); + }); } diff --git a/dev/bots/utils.dart b/dev/bots/utils.dart index 2cd908a983..58bdf5f30b 100644 --- a/dev/bots/utils.dart +++ b/dev/bots/utils.dart @@ -108,9 +108,7 @@ void foundError(List messages) { _pendingLogs.clear(); _errorMessages.add(messages); _hasError = true; - if (onError != null) { - onError!(); - } + onError?.call(); } @visibleForTesting diff --git a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart index 884e86d5ab..930d44d9ef 100644 --- a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart +++ b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart @@ -489,7 +489,7 @@ class _SegmentedControlState extends State= 2); - int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1); // ignore_clamp_double_lint + int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1); switch (Directionality.of(context)) { case TextDirection.ltr: diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index bda8513ebf..eefabb4918 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -358,7 +358,7 @@ class _RawMaterialButtonState extends State with MaterialStat right: densityAdjustment.dx, bottom: densityAdjustment.dy, ), - ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint + ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); final Widget result = ConstrainedBox( diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 1dc623e9cb..e51d5a1717 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -351,7 +351,7 @@ class _ButtonStyleState extends State with TickerProviderStat final double dx = math.max(0, densityAdjustment.dx); final EdgeInsetsGeometry padding = resolvedPadding! .add(EdgeInsets.fromLTRB(dx, dy, dx, dy)) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // If an opaque button's background is becoming translucent while its // elevation is changing, change the elevation first. Material implicitly diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 9bd304c7b8..2662248bbb 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -3417,7 +3417,7 @@ class _MenuPanelState extends State<_MenuPanel> { final double dx = math.max(0, densityAdjustment.dx); final EdgeInsetsGeometry resolvedPadding = padding .add(EdgeInsets.symmetric(horizontal: dx, vertical: dy)) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); BoxConstraints effectiveConstraints = visualDensity.effectiveConstraints( BoxConstraints( @@ -3562,7 +3562,7 @@ class _Submenu extends StatelessWidget { final double dx = math.max(0, densityAdjustment.dx); final EdgeInsetsGeometry resolvedPadding = padding .add(EdgeInsets.fromLTRB(dx, dy, dx, dy)) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); return Theme( data: Theme.of(context).copyWith( diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index b3a2c7a73f..6bcdbc770c 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -617,7 +617,7 @@ class PaginatedDataTableState extends State { ), if (!widget.showEmptyRows) SizedBox( - height: (widget.dataRowMaxHeight ?? kMinInteractiveDimension) * (widget.rowsPerPage - _rowCount + _firstRowIndex).clamp(0, widget.rowsPerPage)), // ignore_clamp_double_lint + height: (widget.dataRowMaxHeight ?? kMinInteractiveDimension) * (widget.rowsPerPage - _rowCount + _firstRowIndex).clamp(0, widget.rowsPerPage)), DefaultTextStyle( style: footerTextStyle!, child: IconTheme.merge( diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 0b97b74a48..dba4356c33 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -531,8 +531,8 @@ class _IndicatorPainter extends CustomPainter { final double index = controller.index.toDouble(); final double value = controller.animation!.value; final bool ltr = index > value; - final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex); // ignore_clamp_double_lint - final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex); // ignore_clamp_double_lint + final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex); + final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex); final Rect fromRect = indicatorRect(size, from); final Rect toRect = indicatorRect(size, to); _currentRect = Rect.lerp(fromRect, toRect, (value - from).abs()); diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 6b22f6740b..e1942cd7f9 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -1052,7 +1052,7 @@ class _TextFieldState extends State with RestorationMixin implements if (widget.maxLength! > 0) { // Show the maxLength in the counter counterText += '/${widget.maxLength}'; - final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); // ignore_clamp_double_lint + final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); semanticCounterText = localizations.remainingTextFieldCharacterCount(remaining); } diff --git a/packages/flutter/lib/src/painting/border_radius.dart b/packages/flutter/lib/src/painting/border_radius.dart index 8df870cbc7..0c23b0a7d9 100644 --- a/packages/flutter/lib/src/painting/border_radius.dart +++ b/packages/flutter/lib/src/painting/border_radius.dart @@ -400,10 +400,10 @@ class BorderRadius extends BorderRadiusGeometry { // RRects don't make sense. return RRect.fromRectAndCorners( rect, - topLeft: topLeft.clamp(minimum: Radius.zero), // ignore_clamp_double_lint - topRight: topRight.clamp(minimum: Radius.zero), // ignore_clamp_double_lint - bottomLeft: bottomLeft.clamp(minimum: Radius.zero), // ignore_clamp_double_lint - bottomRight: bottomRight.clamp(minimum: Radius.zero), // ignore_clamp_double_lint + topLeft: topLeft.clamp(minimum: Radius.zero), + topRight: topRight.clamp(minimum: Radius.zero), + bottomLeft: bottomLeft.clamp(minimum: Radius.zero), + bottomRight: bottomRight.clamp(minimum: Radius.zero), ); } diff --git a/packages/flutter/lib/src/painting/box_border.dart b/packages/flutter/lib/src/painting/box_border.dart index 7d2e5bab0c..c19441abb6 100644 --- a/packages/flutter/lib/src/painting/box_border.dart +++ b/packages/flutter/lib/src/painting/box_border.dart @@ -286,10 +286,10 @@ abstract class BoxBorder extends ShapeBorder { rect.top - insets.top, rect.right + insets.right, rect.bottom + insets.bottom, - topLeft: (rect.tlRadius + Radius.elliptical(insets.left, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint - topRight: (rect.trRadius + Radius.elliptical(insets.right, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint - bottomRight: (rect.brRadius + Radius.elliptical(insets.right, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint - bottomLeft: (rect.blRadius + Radius.elliptical(insets.left, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint + topLeft: (rect.tlRadius + Radius.elliptical(insets.left, insets.top)).clamp(minimum: Radius.zero), + topRight: (rect.trRadius + Radius.elliptical(insets.right, insets.top)).clamp(minimum: Radius.zero), + bottomRight: (rect.brRadius + Radius.elliptical(insets.right, insets.bottom)).clamp(minimum: Radius.zero), + bottomLeft: (rect.blRadius + Radius.elliptical(insets.left, insets.bottom)).clamp(minimum: Radius.zero), ); } @@ -299,10 +299,10 @@ abstract class BoxBorder extends ShapeBorder { rect.top + insets.top, rect.right - insets.right, rect.bottom - insets.bottom, - topLeft: (rect.tlRadius - Radius.elliptical(insets.left, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint - topRight: (rect.trRadius - Radius.elliptical(insets.right, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint - bottomRight: (rect.brRadius - Radius.elliptical(insets.right, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint - bottomLeft:(rect.blRadius - Radius.elliptical(insets.left, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint + topLeft: (rect.tlRadius - Radius.elliptical(insets.left, insets.top)).clamp(minimum: Radius.zero), + topRight: (rect.trRadius - Radius.elliptical(insets.right, insets.top)).clamp(minimum: Radius.zero), + bottomRight: (rect.brRadius - Radius.elliptical(insets.right, insets.bottom)).clamp(minimum: Radius.zero), + bottomLeft:(rect.blRadius - Radius.elliptical(insets.left, insets.bottom)).clamp(minimum: Radius.zero), ); } diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 625e102b36..f85e10436d 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -1006,7 +1006,7 @@ class TextStyle with Diagnosticable { fontFamily: fontFamily ?? _fontFamily, fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback, fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta, - fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], // ignore_clamp_double_lint + fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], fontStyle: fontStyle ?? this.fontStyle, letterSpacing: letterSpacing == null ? null : letterSpacing! * letterSpacingFactor + letterSpacingDelta, wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta, diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 713d07c3a4..1dcd494208 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -2054,8 +2054,8 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM @override TextSelection getLineAtOffset(TextPosition position) { final TextRange line = paragraph._getLineAtOffset(position); - final int start = line.start.clamp(range.start, range.end); // ignore_clamp_double_lint - final int end = line.end.clamp(range.start, range.end); // ignore_clamp_double_lint + final int start = line.start.clamp(range.start, range.end); + final int end = line.end.clamp(range.start, range.end); return TextSelection(baseOffset: start, extentOffset: end); } diff --git a/packages/flutter/lib/src/services/text_formatter.dart b/packages/flutter/lib/src/services/text_formatter.dart index 8d013f22a9..af0bf80147 100644 --- a/packages/flutter/lib/src/services/text_formatter.dart +++ b/packages/flutter/lib/src/services/text_formatter.dart @@ -419,7 +419,7 @@ class FilteringTextInputFormatter extends TextInputFormatter { // The length added by adding the replacementString. final int replacedLength = originalIndex <= regionStart && originalIndex < regionEnd ? 0 : replacementString.length; // The length removed by removing the replacementRange. - final int removedLength = originalIndex.clamp(regionStart, regionEnd) - regionStart; // ignore_clamp_double_lint + final int removedLength = originalIndex.clamp(regionStart, regionEnd) - regionStart; return replacedLength - removedLength; } diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index ae7efa8ff0..28d5cbf7c3 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -889,7 +889,7 @@ class TextEditingValue { // The length added by adding the replacementString. final int replacedLength = originalIndex <= replacementRange.start && originalIndex < replacementRange.end ? 0 : replacementString.length; // The length removed by removing the replacementRange. - final int removedLength = originalIndex.clamp(replacementRange.start, replacementRange.end) - replacementRange.start; // ignore_clamp_double_lint + final int removedLength = originalIndex.clamp(replacementRange.start, replacementRange.end) - replacementRange.start; return originalIndex + replacedLength - removedLength; } diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index de234ceab5..20fec44e0b 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -841,7 +841,7 @@ class _AnimatedPaddingState extends AnimatedWidgetBaseState { return Padding( padding: _padding! .evaluate(animation) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), // ignore_clamp_double_lint + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), child: widget.child, ); }