Unverified Commit aa609127 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Use dart analyze package for `num.clamp` (#139867)

Extacted from #130101, dropped the `@_debugAssert` stuff from that PR so it's easier to review.
parent 44beb843
......@@ -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 {
......
// 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);
}
// 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,
);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment