flutter/dev/bots/custom_rules/render_box_intrinsics.dart
Michael Goderbauer 5491c8c146
Auto-format Framework (#160545)
This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.

**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
2024-12-19 20:06:21 +00:00

125 lines
4.6 KiB
Dart

// 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 [candidates] variable contains the
/// full list of RenderBox intrinsic method invocations checked by this rule.
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));
}
}
}