| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'package:analyzer/dart/analysis/results.dart' ; |
| 6 | import 'package:analyzer/dart/ast/ast.dart' ; |
| 7 | import 'package:analyzer/dart/ast/visitor.dart' ; |
| 8 | import 'package:analyzer/dart/element/element.dart' ; |
| 9 | import 'package:analyzer/dart/element/type.dart' ; |
| 10 | |
| 11 | import '../utils.dart'; |
| 12 | import 'analyze.dart'; |
| 13 | |
| 14 | /// Verify that we use clampDouble instead of double.clamp for performance |
| 15 | /// reasons. |
| 16 | /// |
| 17 | /// See also: |
| 18 | /// * https://github.com/flutter/flutter/pull/103559 |
| 19 | /// * https://github.com/flutter/flutter/issues/103917 |
| 20 | final AnalyzeRule noDoubleClamp = _NoDoubleClamp(); |
| 21 | |
| 22 | class _NoDoubleClamp implements AnalyzeRule { |
| 23 | final Map<ResolvedUnitResult, List<AstNode>> _errors = <ResolvedUnitResult, List<AstNode>>{}; |
| 24 | |
| 25 | @override |
| 26 | void applyTo(ResolvedUnitResult unit) { |
| 27 | final _DoubleClampVisitor visitor = _DoubleClampVisitor(); |
| 28 | unit.unit.visitChildren(visitor); |
| 29 | final List<AstNode> violationsInUnit = visitor.clampAccessNodes; |
| 30 | if (violationsInUnit.isNotEmpty) { |
| 31 | _errors.putIfAbsent(unit, () => <AstNode>[]).addAll(violationsInUnit); |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | @override |
| 36 | void reportViolations(String workingDirectory) { |
| 37 | if (_errors.isEmpty) { |
| 38 | return; |
| 39 | } |
| 40 | |
| 41 | foundError(<String>[ |
| 42 | for (final MapEntry<ResolvedUnitResult, List<AstNode>> entry in _errors.entries) |
| 43 | for (final AstNode node in entry.value) |
| 44 | ' ${locationInFile(entry.key, node, workingDirectory)}: ${node.parent}' , |
| 45 | '\n ${bold}For performance reasons, we use a custom "clampDouble" function instead of using "double.clamp". $reset' , |
| 46 | ]); |
| 47 | } |
| 48 | |
| 49 | @override |
| 50 | String toString() => 'No "double.clamp"' ; |
| 51 | } |
| 52 | |
| 53 | class _DoubleClampVisitor extends RecursiveAstVisitor<void> { |
| 54 | final List<AstNode> clampAccessNodes = <AstNode>[]; |
| 55 | |
| 56 | // We don't care about directives or comments. |
| 57 | @override |
| 58 | void visitImportDirective(ImportDirective node) {} |
| 59 | |
| 60 | @override |
| 61 | void visitExportDirective(ExportDirective node) {} |
| 62 | |
| 63 | @override |
| 64 | void visitComment(Comment node) {} |
| 65 | |
| 66 | @override |
| 67 | void visitSimpleIdentifier(SimpleIdentifier node) { |
| 68 | if (node.name != 'clamp' || node.staticElement is! MethodElement) { |
| 69 | return; |
| 70 | } |
| 71 | final bool isAllowed = switch (node.parent) { |
| 72 | // PropertyAccess matches num.clamp in tear-off form. Always prefer |
| 73 | // doubleClamp over tear-offs: even when all 3 operands are int literals, |
| 74 | // the return type doesn't get promoted to int: |
| 75 | // final x = 1.clamp(0, 2); // The inferred return type is int, where as: |
| 76 | // final f = 1.clamp; |
| 77 | // final y = f(0, 2) // The inferred return type is num. |
| 78 | PropertyAccess( |
| 79 | target: Expression( |
| 80 | staticType: DartType(isDartCoreDouble: true) || |
| 81 | DartType(isDartCoreNum: true) || |
| 82 | DartType(isDartCoreInt: true), |
| 83 | ), |
| 84 | ) => |
| 85 | false, |
| 86 | |
| 87 | // Expressions like `final int x = 1.clamp(0, 2);` should be allowed. |
| 88 | MethodInvocation( |
| 89 | target: Expression(staticType: DartType(isDartCoreInt: true)), |
| 90 | argumentList: ArgumentList( |
| 91 | arguments: [ |
| 92 | Expression(staticType: DartType(isDartCoreInt: true)), |
| 93 | Expression(staticType: DartType(isDartCoreInt: true)), |
| 94 | ], |
| 95 | ), |
| 96 | ) => |
| 97 | true, |
| 98 | |
| 99 | // Otherwise, disallow num.clamp() invocations. |
| 100 | MethodInvocation( |
| 101 | target: Expression( |
| 102 | staticType: DartType(isDartCoreDouble: true) || |
| 103 | DartType(isDartCoreNum: true) || |
| 104 | DartType(isDartCoreInt: true), |
| 105 | ), |
| 106 | ) => |
| 107 | false, |
| 108 | |
| 109 | _ => true, |
| 110 | }; |
| 111 | if (!isAllowed) { |
| 112 | clampAccessNodes.add(node); |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |