mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

https://github.com/flutter/flutter/pull/163567#issuecomment-2669969018 The old name is deprecated and removed. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
125 lines
4.6 KiB
Dart
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?.enclosingElement3;
|
|
if (declaredInClassElement is InterfaceElement &&
|
|
_implementsRenderBox(declaredInClassElement)) {
|
|
violationNodes.add((node, correctMethodName));
|
|
}
|
|
}
|
|
}
|