Use dart analyze package for num.clamp
(#139867)
Extacted from #130101, dropped the `@_debugAssert` stuff from that PR so it's easier to review.
This commit is contained in:
parent
44beb843aa
commit
aa609127e7
@ -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<void> run(List<String> 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<void> run(List<String> arguments) async {
|
||||
|
||||
// Analyze all the Dart code in the repo.
|
||||
printProgress('Dart analysis...');
|
||||
await _runFlutterAnalyze(flutterRoot, options: <String>[
|
||||
final CommandResult dartAnalyzeResult = await _runFlutterAnalyze(flutterRoot, options: <String>[
|
||||
'--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<AnalyzeRule> rules = <AnalyzeRule>[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<CompilationUnit> {
|
||||
_DoubleClampVisitor(this.parseResult);
|
||||
|
||||
final List<_Line> clamps = <_Line>[];
|
||||
final ParseStringResult parseResult;
|
||||
|
||||
@override
|
||||
CompilationUnit? visitMethodInvocation(MethodInvocation node) {
|
||||
final NodeList<Expression> 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<void> verifyTargetPlatform(String workingDirectory) async {
|
||||
final File framework = File('$workingDirectory/packages/flutter/lib/src/foundation/platform.dart');
|
||||
final Set<String> frameworkPlatforms = <String>{};
|
||||
@ -350,41 +333,6 @@ Future<void> 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<void> verifyNoDoubleClamp(String workingDirectory) async {
|
||||
final String flutterLibPath = '$workingDirectory/packages/flutter/lib';
|
||||
final Stream<File> testFiles =
|
||||
_allFiles(flutterLibPath, 'dart', minimumMatches: 100);
|
||||
final List<String> errors = <String>[];
|
||||
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(<String>[
|
||||
...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<void> verifyTokenTemplatesUpdateCorrectFiles(String workingDirectory) async {
|
||||
|
79
dev/bots/custom_rules/analyze.dart
Normal file
79
dev/bots/custom_rules/analyze.dart
Normal file
@ -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<void> analyzeFrameworkWithRules(String flutterRootDirectory, List<AnalyzeRule> rules) async {
|
||||
final String flutterLibPath = path.canonicalize('$flutterRootDirectory/packages/flutter/lib');
|
||||
if (!Directory(flutterLibPath).existsSync()) {
|
||||
foundError(<String>['Analyzer error: the specified $flutterLibPath does not exist.']);
|
||||
}
|
||||
final AnalysisContextCollection collection = AnalysisContextCollection(
|
||||
includedPaths: <String>[flutterLibPath],
|
||||
excludedPaths: <String>[path.canonicalize('$flutterLibPath/fix_data')],
|
||||
);
|
||||
|
||||
final List<String> analyzerErrors = <String>[];
|
||||
for (final AnalysisContext context in collection.contexts) {
|
||||
final Iterable<String> 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);
|
||||
}
|
104
dev/bots/custom_rules/no_double_clamp.dart
Normal file
104
dev/bots/custom_rules/no_double_clamp.dart
Normal file
@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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,
|
||||
<AnalyzeRule>[noDoubleClamp],
|
||||
), shouldHaveErrors: true);
|
||||
final String lines = <String>[
|
||||
'║ 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'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -108,9 +108,7 @@ void foundError(List<String> messages) {
|
||||
_pendingLogs.clear();
|
||||
_errorMessages.add(messages);
|
||||
_hasError = true;
|
||||
if (onError != null) {
|
||||
onError!();
|
||||
}
|
||||
onError?.call();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
|
@ -489,7 +489,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
|
||||
final int numOfChildren = widget.children.length;
|
||||
assert(renderBox.hasSize);
|
||||
assert(numOfChildren >= 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:
|
||||
|
@ -358,7 +358,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> 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(
|
||||
|
@ -351,7 +351,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> 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
|
||||
|
@ -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(
|
||||
|
@ -617,7 +617,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
|
||||
),
|
||||
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(
|
||||
|
@ -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());
|
||||
|
@ -1052,7 +1052,7 @@ class _TextFieldState extends State<TextField> 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);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -841,7 +841,7 @@ class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> {
|
||||
return Padding(
|
||||
padding: _padding!
|
||||
.evaluate(animation)
|
||||
.clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), // ignore_clamp_double_lint
|
||||
.clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user