Add @protected to public State method overrides (#157313)

I don't want to see these methods in my autofill suggestions.

<br>

![navigator
dispose](https://github.com/user-attachments/assets/5d2dc37c-abe9-44f4-ad16-4396a3aeeaf7)
This commit is contained in:
Nate Wilson
2024-11-18 14:28:08 -07:00
committed by GitHub
parent ff47d63235
commit c25790e3db
26 changed files with 223 additions and 5 deletions

View File

@@ -22,6 +22,7 @@ import 'custom_rules/analyze.dart';
import 'custom_rules/avoid_future_catcherror.dart';
import 'custom_rules/no_double_clamp.dart';
import 'custom_rules/no_stop_watches.dart';
import 'custom_rules/protect_public_state_subtypes.dart';
import 'custom_rules/render_box_intrinsics.dart';
import 'run_command.dart';
import 'utils.dart';
@@ -180,7 +181,12 @@ Future<void> run(List<String> arguments) async {
// Only run the private lints when the code is free of type errors. The
// lints are easier to write when they can assume, for example, there is no
// inheritance cycles.
final List<AnalyzeRule> rules = <AnalyzeRule>[noDoubleClamp, noStopwatches, renderBoxIntrinsicCalculation];
final List<AnalyzeRule> rules = <AnalyzeRule>[
noDoubleClamp,
noStopwatches,
renderBoxIntrinsicCalculation,
protectPublicStateSubtypes,
];
final String ruleNames = rules.map((AnalyzeRule rule) => '\n * $rule').join();
printProgress('Analyzing code in the framework with the following rules:$ruleNames');
await analyzeWithRules(flutterRoot, rules,

View File

@@ -0,0 +1,101 @@
// 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.
// TODO(nate-thegrate): remove this file if @protected changes, or add a test if it doesn't.
// https://github.com/dart-lang/sdk/issues/57094
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';
final AnalyzeRule protectPublicStateSubtypes = _ProtectPublicStateSubtypes();
class _ProtectPublicStateSubtypes implements AnalyzeRule {
final Map<ResolvedUnitResult, List<MethodDeclaration>> _errors = <ResolvedUnitResult, List<MethodDeclaration>>{};
@override
void applyTo(ResolvedUnitResult unit) {
final _StateSubclassVisitor visitor = _StateSubclassVisitor();
unit.unit.visitChildren(visitor);
final List<MethodDeclaration> unprotected = visitor.unprotectedMethods;
if (unprotected.isNotEmpty) {
_errors.putIfAbsent(unit, () => <MethodDeclaration>[]).addAll(unprotected);
}
}
@override
void reportViolations(String workingDirectory) {
if (_errors.isEmpty) {
return;
}
foundError(
<String>[
for (final MapEntry<ResolvedUnitResult, List<MethodDeclaration>> entry in _errors.entries)
for (final MethodDeclaration method in entry.value)
'${locationInFile(entry.key, method, workingDirectory)}: $method - missing "@protected" annotation.',
'\nPublic State subtypes should add @protected when overriding methods,',
'to avoid exposing internal logic to developers.',
],
);
}
@override
String toString() => 'Add "@protected" to public State subtypes';
}
class _StateSubclassVisitor extends SimpleAstVisitor<void> {
final List<MethodDeclaration> unprotectedMethods = <MethodDeclaration>[];
/// Holds the `State` class [DartType].
static DartType? stateType;
static bool isPublicStateSubtype(InterfaceElement element) {
if (!element.isPublic) {
return false;
}
if (stateType != null) {
return element.allSupertypes.contains(stateType);
}
for (final InterfaceType superType in element.allSupertypes) {
if (superType.element.name == 'State') {
stateType = superType;
return true;
}
}
return false;
}
@override
void visitClassDeclaration(ClassDeclaration node) {
if (isPublicStateSubtype(node.declaredElement!)) {
node.visitChildren(this);
}
}
/// Checks whether overridden `State` methods have the `@protected` annotation,
/// and adds the declaration to [unprotectedMethods] if not.
@override
void visitMethodDeclaration(MethodDeclaration node) {
switch (node.name.lexeme) {
case 'initState':
case 'didUpdateWidget':
case 'didChangeDependencies':
case 'reassemble':
case 'deactivate':
case 'activate':
case 'dispose':
case 'build':
case 'debugFillProperties':
if (!node.declaredElement!.hasProtected) {
unprotectedMethods.add(node);
}
}
}
}