| // 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 '../utils.dart'; |
| import 'analyze.dart'; |
| |
| /// Verify that no RenderBox subclasses call compute* instead of get* for |
| /// computing the intrinsic dimensions. The full list of RenderBox intrinsic |
| /// methods checked by this rule is listed in [candidates]. |
| final AnalyzeRule renderBoxIntrinsicCalculation = _RenderBoxIntrinsicCalculationRule(); |
| |
| const Map<String, String> candidates = <String, String> { |
| 'computeDryBaseline': 'getDryBaseline', |
| 'computeDryLayout': 'getDryLayout', |
| 'computeDistanceToActualBaseline': 'getDistanceToBaseline, or getDistanceToActualBaseline', |
| 'computeMaxIntrinsicHeight': 'getMaxIntrinsicHeight', |
| 'computeMinIntrinsicHeight': 'getMinIntrinsicHeight', |
| 'computeMaxIntrinsicWidth': 'getMaxIntrinsicWidth', |
| 'computeMinIntrinsicWidth': 'getMinIntrinsicWidth' |
| }; |
| |
| class _RenderBoxIntrinsicCalculationRule implements AnalyzeRule { |
| final Map<ResolvedUnitResult, List<(AstNode, String)>> _errors = <ResolvedUnitResult, List<(AstNode, String)>>{}; |
| |
| @override |
| void applyTo(ResolvedUnitResult unit) { |
| final _RenderBoxSubclassVisitor visitor = _RenderBoxSubclassVisitor(); |
| unit.unit.visitChildren(visitor); |
| final List<(AstNode, String)> violationsInUnit = visitor.violationNodes; |
| if (violationsInUnit.isNotEmpty) { |
| _errors.putIfAbsent(unit, () => <(AstNode, String)>[]).addAll(violationsInUnit); |
| } |
| } |
| |
| @override |
| void reportViolations(String workingDirectory) { |
| if (_errors.isEmpty) { |
| return; |
| } |
| |
| foundError(<String>[ |
| for (final MapEntry<ResolvedUnitResult, List<(AstNode, String)>> entry in _errors.entries) |
| for (final (AstNode node, String suggestion) in entry.value) |
| '${locationInFile(entry.key, node, workingDirectory)}: ${node.parent}. Consider calling $suggestion instead.', |
| '\n${bold}Typically the get* methods should be used to obtain the intrinsics of a RenderBox.$reset', |
| ]); |
| } |
| |
| @override |
| String toString() => 'RenderBox subclass intrinsic calculation best practices'; |
| } |
| |
| class _RenderBoxSubclassVisitor extends RecursiveAstVisitor<void> { |
| final List<(AstNode, String)> violationNodes = <(AstNode, String)>[]; |
| |
| static final Map<InterfaceElement, bool> _isRenderBoxClassElementCache = <InterfaceElement, bool>{}; |
| // The cached version, call this method instead of _checkIfImplementsRenderBox. |
| static bool _implementsRenderBox(InterfaceElement interfaceElement) { |
| // Framework naming convention: a RenderObject subclass names have "Render" in its name. |
| if (!interfaceElement.name.contains('Render')) { |
| return false; |
| } |
| return interfaceElement.name == 'RenderBox' |
| || _isRenderBoxClassElementCache.putIfAbsent(interfaceElement, () => _checkIfImplementsRenderBox(interfaceElement)); |
| } |
| |
| static bool _checkIfImplementsRenderBox(InterfaceElement element) { |
| return element.allSupertypes.any((InterfaceType interface) => _implementsRenderBox(interface.element)); |
| } |
| |
| // We don't care about directives, comments, or asserts. |
| @override |
| void visitImportDirective(ImportDirective node) { } |
| @override |
| void visitExportDirective(ExportDirective node) { } |
| @override |
| void visitComment(Comment node) { } |
| @override |
| void visitAssertStatement(AssertStatement node) { } |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| // Ignore the RenderBox class implementation: that's the only place the |
| // compute* methods are supposed to be called. |
| if (node.name.lexeme != 'RenderBox') { |
| super.visitClassDeclaration(node); |
| } |
| } |
| |
| @override |
| void visitSimpleIdentifier(SimpleIdentifier node) { |
| final String? correctMethodName = candidates[node.name]; |
| if (correctMethodName == null) { |
| return; |
| } |
| final bool isCallingSuperImplementation = switch (node.parent) { |
| PropertyAccess(target: SuperExpression()) || |
| MethodInvocation(target: SuperExpression()) => true, |
| _ => false, |
| }; |
| if (isCallingSuperImplementation) { |
| return; |
| } |
| final Element? declaredInClassElement = node.staticElement?.declaration?.enclosingElement; |
| if (declaredInClassElement is InterfaceElement && _implementsRenderBox(declaredInClassElement)) { |
| violationNodes.add((node, correctMethodName)); |
| } |
| } |
| } |