// 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 candidates = { 'computeDryBaseline': 'getDryBaseline', 'computeDryLayout': 'getDryLayout', 'computeDistanceToActualBaseline': 'getDistanceToBaseline, or getDistanceToActualBaseline', 'computeMaxIntrinsicHeight': 'getMaxIntrinsicHeight', 'computeMinIntrinsicHeight': 'getMinIntrinsicHeight', 'computeMaxIntrinsicWidth': 'getMaxIntrinsicWidth', 'computeMinIntrinsicWidth': 'getMinIntrinsicWidth' }; class _RenderBoxIntrinsicCalculationRule implements AnalyzeRule { final Map> _errors = >{}; @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([ for (final MapEntry> 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 { final List<(AstNode, String)> violationNodes = <(AstNode, String)>[]; static final Map _isRenderBoxClassElementCache = {}; // 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)); } } }