Reverts "[ Analysis ] Added initial implementation of the flutter_analyzer_plugin (#175679)" (#179766)

<!-- start_original_pr_link -->
Reverts: flutter/flutter#175679
<!-- end_original_pr_link -->
<!-- start_initiating_author -->
Initiated by: jtmcdole
<!-- end_initiating_author -->
<!-- start_revert_reason -->
Reason for reverting: Linux linux_unopt is now timing out in postsubmit
<!-- end_revert_reason -->
<!-- start_original_pr_author -->
Original PR Author: bkonyi
<!-- end_original_pr_author -->

<!-- start_reviewers -->
Reviewed By: {srawlins}
<!-- end_reviewers -->

<!-- start_revert_body -->
This change reverts the following previous change:
The `flutter_analyzer_plugin` implements rules previously enforced by
the `dev/bots/analyze.dart` check run on the CI, allowing for earlier
detection of custom lint violations before a change is uploaded for
review.

Currently, the plugin implements the following rules:

  - avoid_future_catch_error
  - no_double_clamp
  - no_stopwatches
  - protect_public_state_subtypes
  - render_box_intrinsics

Towards https://github.com/flutter/flutter/issues/175276
<!-- end_revert_body -->

Co-authored-by: auto-submit[bot] <flutter-engprod-team@google.com>
This commit is contained in:
auto-submit[bot]
2025-12-12 00:13:27 +00:00
committed by GitHub
parent 2e5ed1aea7
commit f5b530930a
27 changed files with 14 additions and 1361 deletions

View File

@@ -13,16 +13,6 @@
# This file contains the analysis options used for code in the flutter/flutter
# repository.
plugins:
flutter_analyzer_plugin:
path: dev/flutter_analyzer_plugin
diagnostics:
avoid_future_catch_error: false
no_double_clamp: false
no_stopwatches: false
protect_public_state_subtypes: false
render_box_intrinsics: false
analyzer:
language:
strict-casts: true
@@ -34,7 +24,6 @@ analyzer:
# something (https://github.com/flutter/flutter/issues/143312)
deprecated_member_use: ignore
deprecated_member_use_from_same_package: ignore
plugins_in_inner_options: ignore
exclude:
- "bin/cache/**"
# Ignore protoc generated files

View File

@@ -298,7 +298,6 @@ Future<void> frameworkTestsRunner() async {
path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'),
fatalWarnings: false,
);
await runDartTest(path.join(flutterRoot, 'dev', 'flutter_analyzer_plugin'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'ui'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'tools'));

View File

@@ -2,15 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
abstract class RenderBox {
void computeDryBaseline() {}
void computeDryLayout() {}
void computeDistanceToActualBaseline() {}
void computeMaxIntrinsicHeight() {}
void computeMinIntrinsicHeight() {}
void computeMaxIntrinsicWidth() {}
void computeMinIntrinsicWidth() {}
}
import '../../foo/fake_render_box.dart';
mixin ARenderBoxMixin on RenderBox {
@override

View File

@@ -0,0 +1,13 @@
// 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.
abstract class RenderBox {
void computeDryBaseline() {}
void computeDryLayout() {}
void computeDistanceToActualBaseline() {}
void computeMaxIntrinsicHeight() {}
void computeMinIntrinsicHeight() {}
void computeMaxIntrinsicWidth() {}
void computeMinIntrinsicWidth() {}
}

View File

@@ -1,3 +0,0 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/

View File

@@ -1,7 +0,0 @@
# Flutter Analyzer Plugin
This plugin provides custom lint rules specific to development within flutter/flutter.
This plugin is a WIP as cases covered by `dev/bots/analyze.dart` are ported to this plugin,
with the eventual goal of implementing as many of the checks as possible to reduce the number
of analysis failures only discovered by CI checks.

View File

@@ -1,5 +0,0 @@
include: ../analysis_options.yaml
linter:
rules:
unawaited_futures: true

View File

@@ -1,28 +0,0 @@
// 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:analysis_server_plugin/plugin.dart';
import 'package:analysis_server_plugin/registry.dart';
import 'src/rules/avoid_future_catch_error.dart';
import 'src/rules/no_double_clamp.dart';
import 'src/rules/no_stopwatches.dart';
import 'src/rules/protect_public_state_subtypes.dart';
import 'src/rules/render_box_intrinsics.dart';
final FlutterAnalyzerPlugin plugin = FlutterAnalyzerPlugin();
class FlutterAnalyzerPlugin extends Plugin {
@override
void register(PluginRegistry registry) {
registry
..registerWarningRule(AvoidFutureCatchError())
..registerWarningRule(NoDoubleClamp())
..registerWarningRule(NoStopwatches())
..registerWarningRule(ProtectPublicStateSubtypes())
..registerWarningRule(RenderBoxIntrinsicCalculationRule());
}
@override
String get name => 'flutter/flutter analyzer plugin';
}

View File

@@ -1,52 +0,0 @@
// 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/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
class AvoidFutureCatchError extends AnalysisRule {
AvoidFutureCatchError()
: super(
name: code.name,
description: 'Future.catchError and Future.onError are not type safe.',
);
static const LintCode code = LintCode(
'avoid_future_catch_error',
'Avoid using Future.catchError',
correctionMessage: 'Use Future.then instead (https://github.com/dart-lang/sdk/issues/51248).',
severity: DiagnosticSeverity.ERROR,
);
@override
LintCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) {
final visitor = _Visitor(this, context);
registry.addMethodInvocation(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
_Visitor(this.rule, this.context);
final AnalysisRule rule;
final RuleContext context;
@override
void visitMethodInvocation(MethodInvocation node) {
if (node case MethodInvocation(
methodName: SimpleIdentifier(name: 'onError' || 'catchError'),
realTarget: Expression(staticType: DartType(isDartAsyncFuture: true)),
)) {
rule.reportAtNode(node);
}
}
}

View File

@@ -1,100 +0,0 @@
// 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/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.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 'package:analyzer/error/error.dart';
/// Verify that we use clampDouble instead of double.clamp for performance
/// reasons.
///
/// See also:
/// * https://github.com/flutter/flutter/pull/103559
/// * https://github.com/flutter/flutter/issues/103917
class NoDoubleClamp extends AnalysisRule {
NoDoubleClamp()
: super(
name: code.name,
description:
'Verify that we use clampDouble instead of double.clamp for performance reasons.',
);
static const LintCode code = LintCode(
'no_double_clamp',
'Avoid double.clamp for performance reasons.',
correctionMessage: 'Use clampDouble instead.',
severity: DiagnosticSeverity.ERROR,
);
@override
DiagnosticCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) {
final visitor = _Visitor(this, context);
registry.addSimpleIdentifier(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
_Visitor(this.rule, this.context);
final AnalysisRule rule;
final RuleContext context;
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.name != 'clamp' || node.element is! MethodElement) {
return;
}
final bool isAllowed = switch (node.parent) {
// PropertyAccess matches num.clamp in tear-off form. Always prefer
// doubleClamp over tear-offs: even when all 3 operands are int literals,
// the return type doesn't get promoted to int:
// final x = 1.clamp(0, 2); // The inferred return type is int, where as:
// final f = 1.clamp;
// final y = f(0, 2) // The inferred return type is num.
PropertyAccess(
target: Expression(
staticType: DartType(isDartCoreDouble: true) ||
DartType(isDartCoreNum: true) ||
DartType(isDartCoreInt: true),
),
) =>
false,
// Expressions like `final int x = 1.clamp(0, 2);` should be allowed.
MethodInvocation(
target: Expression(staticType: DartType(isDartCoreInt: true)),
argumentList: ArgumentList(
arguments: [
Expression(staticType: DartType(isDartCoreInt: true)),
Expression(staticType: DartType(isDartCoreInt: true)),
],
),
) =>
true,
// Otherwise, disallow num.clamp() invocations.
MethodInvocation(
target: Expression(
staticType: DartType(isDartCoreDouble: true) ||
DartType(isDartCoreNum: true) ||
DartType(isDartCoreInt: true),
),
) =>
false,
_ => true,
};
if (!isAllowed) {
rule.reportAtNode(node);
}
}
}

View File

@@ -1,127 +0,0 @@
// 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/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.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 'package:analyzer/error/error.dart';
import 'package:path/path.dart' as path;
// The comment pattern representing the "flutter_ignore" inline directive that
// indicates the line should be exempt from the stopwatch check.
final Pattern _ignoreStopwatch = RegExp(r'// flutter_ignore: .*stopwatch .*\(see analyze\.dart\)');
/// Use of Stopwatches can introduce test flakes as the logical time of a
/// stopwatch can fall out of sync with the mocked time of FakeAsync in testing.
/// The Clock object provides a safe stopwatch instead, which is paired with
/// FakeAsync as part of the test binding.
class NoStopwatches extends AnalysisRule {
NoStopwatches() : super(name: code.name, description: ruleDescription);
static const String ruleDescription =
'Use of Stopwatches can introduce test flakes as the logical time of a stopwatch can fall '
'out of sync with the mocked time of FakeAsync in testing.';
static const LintCode code = LintCode(
'no_stopwatches',
ruleDescription,
correctionMessage: 'Use clock.stopwatch() from package:clock instead.',
severity: DiagnosticSeverity.ERROR,
);
@override
DiagnosticCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) {
final visitor = _Visitor(this, context);
registry
..addConstructorName(this, visitor)
..addSimpleIdentifier(this, visitor);
}
}
// This visitor finds invocation sites of Stopwatch (and subclasses) constructors
// and references to "external" functions that return a Stopwatch (and subclasses),
// including constructors.
class _Visitor extends SimpleAstVisitor<void> {
_Visitor(this.rule, this.context);
final AnalysisRule rule;
final RuleContext context;
final Map<ClassElement, bool> _isStopwatchClassElementCache = <ClassElement, bool>{};
bool _checkIfImplementsStopwatchRecursively(ClassElement classElement) {
if (classElement.library.isDartCore) {
return classElement.name == 'Stopwatch';
}
return classElement.allSupertypes.any((InterfaceType interface) {
final InterfaceElement interfaceElement = interface.element;
return interfaceElement is ClassElement && _implementsStopwatch(interfaceElement);
});
}
// The cached version, call this method instead of _checkIfImplementsStopwatchRecursively.
bool _implementsStopwatch(ClassElement classElement) {
return classElement.library.isDartCore
? classElement.name == 'Stopwatch'
: _isStopwatchClassElementCache.putIfAbsent(
classElement,
() => _checkIfImplementsStopwatchRecursively(classElement),
);
}
bool _isInternal(LibraryElement libraryElement) {
return path.isWithin(
libraryElement.session.analysisContext.contextRoot.root.path,
libraryElement.firstFragment.source.fullName,
);
}
bool _hasTrailingFlutterIgnore(AstNode node) {
return context.currentUnit!.content
.substring(
node.offset + node.length,
context.currentUnit!.unit.lineInfo.getOffsetOfLineAfter(node.offset + node.length),
)
.contains(_ignoreStopwatch);
}
@override
void visitConstructorName(ConstructorName node) {
final ConstructorElement element = node.element!;
final bool isAllowed = switch (element.returnType) {
InterfaceType(element: final ClassElement classElement) =>
!_implementsStopwatch(classElement),
InterfaceType(element: InterfaceElement()) => true,
};
if (isAllowed || _hasTrailingFlutterIgnore(node)) {
return;
}
rule.reportAtNode(node);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
final bool isAllowed = switch (node.element) {
ExecutableElement(
returnType: DartType(element: final ClassElement classElement),
library: final LibraryElement libraryElement,
)
// Don't double report constructors and factories.
when node.element is! ConstructorElement =>
_isInternal(libraryElement) || !_implementsStopwatch(classElement),
Element() || null => true,
};
if (isAllowed || _hasTrailingFlutterIgnore(node)) {
return;
}
rule.reportAtNode(node);
}
}

View File

@@ -1,97 +0,0 @@
// 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/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.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 'package:analyzer/error/error.dart';
class ProtectPublicStateSubtypes extends AnalysisRule {
ProtectPublicStateSubtypes()
: super(
name: code.name,
description:
'Public State subtypes should add @protected when overriding methods '
'to avoid exposing internal logic to developers.',
);
static const LintCode code = LintCode(
'protect_public_state_subtypes',
'Public State subtypes should add @protected when overriding methods '
'to avoid exposing internal logic to developers.',
severity: DiagnosticSeverity.ERROR,
);
@override
DiagnosticCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) {
final visitor = _Visitor(this, context);
registry.addClassDeclaration(this, visitor);
}
}
class _Visitor extends RecursiveAstVisitor<void> {
_Visitor(this.rule, this.context);
final AnalysisRule rule;
final RuleContext context;
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.declaredFragment!.element)) {
node.visitChildren(this);
}
}
/// Checks whether overridden `State` methods have the `@protected` annotation,
/// and reports the method if not.
@override
void visitMethodDeclaration(MethodDeclaration node) {
switch (node.name.lexeme) {
case 'initState':
case 'setState':
case 'didUpdateWidget':
case 'didChangeDependencies':
case 'reassemble':
case 'deactivate':
case 'activate':
case 'dispose':
case 'build':
case 'debugFillProperties':
if (!node.declaredFragment!.element.metadata.hasProtected) {
rule.reportAtNode(node);
}
}
}
}

View File

@@ -1,127 +0,0 @@
// 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/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.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.
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 extends AnalysisRule {
RenderBoxIntrinsicCalculationRule()
: super(
name: code.name,
description: 'get* methods should be used to obtain the intrinsics of a RenderBox.',
);
static const LintCode code = LintCode(
'render_box_intrinsics',
'Typically the get* methods should be used to obtain the intrinsics of a RenderBox.',
correctionMessage: 'Consider calling {0} instead.',
severity: DiagnosticSeverity.ERROR,
);
@override
LintCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) {
final visitor = _Visitor(this, context);
registry.addSimpleIdentifier(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
_Visitor(this.rule, this.context);
final AnalysisRule rule;
final RuleContext context;
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),
);
}
static bool _checkIfRenderBoxParent(AstNode? node) {
if (node == null) {
return false;
}
if (node case ClassDeclaration(:final Token name)) {
// Ignore the RenderBox class implementation: that's the only place the
// compute* methods are supposed to be called.
return name.lexeme == 'RenderBox';
}
return _checkIfRenderBoxParent(node.parent);
}
static bool _checkForCommentContext(AstNode? node) {
if (node == null) {
return false;
}
if (node is CommentReference) {
return true;
}
return _checkForCommentContext(node.parent);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.parent is CommentReference) {
return;
}
final String? correctMethodName = candidates[node.name];
if (correctMethodName == null) {
return;
}
if (_checkIfRenderBoxParent(node.parent) || _checkForCommentContext(node.parent)) {
return;
}
final bool isCallingSuperImplementation = switch (node.parent) {
PropertyAccess(target: SuperExpression()) ||
MethodInvocation(target: SuperExpression()) => true,
_ => false,
};
if (isCallingSuperImplementation) {
return;
}
final Element? declaredInClassElement = node.element?.enclosingElement;
if (declaredInClassElement is InterfaceElement &&
_implementsRenderBox(declaredInClassElement)) {
rule.reportAtNode(node, arguments: <Object>[correctMethodName]);
}
}
}

View File

@@ -1,20 +0,0 @@
name: flutter_analyzer_plugin
description: Custom analysis rules for flutter/flutter
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: ^3.7.0
dependencies:
analysis_server_plugin: any
analyzer: any
path: any
dev_dependencies:
analyzer_testing: any
test_reflective_loader: any
# PUBSPEC CHECKSUM: jdpqac

View File

@@ -1,52 +0,0 @@
// 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/src/lint/registry.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:flutter_analyzer_plugin/src/rules/avoid_future_catch_error.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@reflectiveTest
class AvoidFutureCatchErrorTest extends AnalysisRuleTest {
@override
void setUp() {
Registry.ruleRegistry.registerWarningRule(AvoidFutureCatchError());
super.setUp();
}
@override
String get analysisRule => AvoidFutureCatchError.code.name;
static const String source = '''
import 'dart:async';
// This extension isn't picked up from dart:async, so we just fake it.
extension MyFutureExtension<T> on Future<T> {
Future<T> onError<E extends Object>(
FutureOr<T> handleError(E error, StackTrace stackTrace), {
bool test(E error)?,
}) {
return this;
}
}
void main() {
Future<void>.value().catchError((e, st) => null); // ERROR
Future<void>.value().onError((e, st) => null); // ERROR
Future<void>.value().then((_) => null, onError: (e, st) => null); // OK
}
''';
// ignore: non_constant_identifier_names
Future<void> test_avoid_future_catch_error() async {
await assertDiagnostics(source, <ExpectedDiagnostic>[lint(313, 48), lint(374, 45)]);
}
}
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AvoidFutureCatchErrorTest);
});
}

View File

@@ -1,71 +0,0 @@
// 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/src/lint/registry.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:flutter_analyzer_plugin/src/rules/no_double_clamp.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@reflectiveTest
class NoDoubleClampTest extends AnalysisRuleTest {
@override
void setUp() {
Registry.ruleRegistry.registerWarningRule(NoDoubleClamp());
super.setUp();
}
@override
String get analysisRule => NoDoubleClamp.code.name;
static const String source = '''
class ClassWithAClampMethod {
ClassWithAClampMethod clamp(double min, double max) => this;
}
void testNoDoubleClamp(int input) {
final ClassWithAClampMethod nonDoubleClamp = ClassWithAClampMethod();
// ignore: unnecessary_nullable_for_final_variable_declarations
final ClassWithAClampMethod? nonDoubleClamp2 = nonDoubleClamp;
// ignore: unnecessary_nullable_for_final_variable_declarations
final int? nullableInt = input;
final double? nullableDouble = nullableInt?.toDouble();
nonDoubleClamp.clamp(0, 2);
input.clamp(0, 2);
input.clamp(0.0, 2); // ERROR: input.clamp(0.0, 2)
input.toDouble().clamp(0, 2); // ERROR: input.toDouble().clamp(0, 2)
nonDoubleClamp2?.clamp(0, 2);
nullableInt?.clamp(0, 2);
nullableInt?.clamp(0, 2.0); // ERROR: nullableInt?.clamp(0, 2.0)
nullableDouble?.clamp(0, 2); // ERROR: nullableDouble?.clamp(0, 2)
// ignore: unused_local_variable
final ClassWithAClampMethod Function(double, double)? tearOff1 = nonDoubleClamp2?.clamp;
// ignore: unused_local_variable
final num Function(num, num)? tearOff2 = nullableInt?.clamp; // ERROR: nullableInt?.clamp
// ignore: unused_local_variable
final num Function(num, num)? tearOff3 = nullableDouble?.clamp; // ERROR: nullableDouble?.clamp
}
''';
// ignore: non_constant_identifier_names
Future<void> test_no_double_clamp() async {
await assertDiagnostics(source, <ExpectedDiagnostic>[
lint(553, 5),
lint(617, 5),
lint(745, 5),
lint(815, 5),
lint(1084, 5),
lint(1214, 5),
]);
}
}
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(NoDoubleClampTest);
});
}

View File

@@ -1,122 +0,0 @@
// 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/src/lint/registry.dart';
import 'package:analyzer/utilities/package_config_file_builder.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:flutter_analyzer_plugin/src/rules/no_stopwatches.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package_mixins/external_stopwatches_mixin.dart';
@reflectiveTest
class NoStopwatchesTest extends AnalysisRuleTest with ExternalStopwatchesPackage {
@override
void setUp() {
Registry.ruleRegistry.registerWarningRule(NoStopwatches());
super.setUp();
writeTestPackageConfig(PackageConfigFileBuilder()..addExternalStopwatchesPackage(this));
}
@override
String get analysisRule => NoStopwatches.code.name;
static const String source = '''
// 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:external_stopwatches/external_stopwatches.dart' as externallib;
typedef ExternalStopwatchConstructor = externallib.MyStopwatch Function();
class StopwatchAtHome extends Stopwatch {
StopwatchAtHome();
StopwatchAtHome.create() : this();
Stopwatch get stopwatch => this;
}
void testNoStopwatches(Stopwatch stopwatch) {
// OK for now, but we probably want to catch public APIs that take a Stopwatch?
stopwatch.runtimeType;
// Bad: introducing Stopwatch from dart:core.
final Stopwatch localVariable = Stopwatch(); // ERROR: Stopwatch()
// Bad: introducing Stopwatch from dart:core.
Stopwatch().runtimeType; // ERROR: Stopwatch()
(localVariable..runtimeType) // OK: not directly introducing Stopwatch.
.runtimeType;
// Bad: introducing a Stopwatch subclass.
StopwatchAtHome().runtimeType; // ERROR: StopwatchAtHome()
// OK: not directly introducing Stopwatch.
Stopwatch anotherStopwatch = stopwatch;
// Bad: introducing a Stopwatch constructor.
StopwatchAtHome Function() constructor = StopwatchAtHome.new; // ERROR: StopwatchAtHome.new
assert(() {
anotherStopwatch = constructor()..runtimeType;
// Bad: introducing a Stopwatch constructor.
constructor = StopwatchAtHome.create; // ERROR: StopwatchAtHome.create
anotherStopwatch = constructor()..runtimeType;
return true;
}());
anotherStopwatch.runtimeType;
// Bad: introducing an external Stopwatch constructor.
externallib.MyStopwatch.create(); // ERROR: externallib.MyStopwatch.create()
ExternalStopwatchConstructor? externalConstructor;
assert(() {
// Bad: introducing an external Stopwatch constructor.
externalConstructor = externallib.MyStopwatch.new; // ERROR: externallib.MyStopwatch.new
return true;
}());
externalConstructor?.call();
// Bad: introducing an external Stopwatch.
externallib.stopwatch.runtimeType; // ERROR: externallib.stopwatch
// Bad: calling an external function that returns a Stopwatch.
externallib.createMyStopwatch().runtimeType; // ERROR: externallib.createMyStopwatch()
// Bad: calling an external function that returns a Stopwatch.
externallib.createStopwatch().runtimeType; // ERROR: externallib.createStopwatch()
// Bad: introducing the tear-off form of an external function that returns a Stopwatch.
externalConstructor = externallib.createMyStopwatch; // ERROR: externallib.createMyStopwatch
// OK: existing instance.
constructor.call().stopwatch;
}
void testStopwatchIgnore(Stopwatch stopwatch) {
Stopwatch().runtimeType; // flutter_ignore: stopwatch (see analyze.dart)
Stopwatch().runtimeType; // flutter_ignore: some_other_ignores, stopwatch (see analyze.dart)
}
''';
// ignore: unreachable_from_main, non_constant_identifier_names
Future<void> fail_test_no_stopwatches() async {
await assertDiagnostics(source, <ExpectedDiagnostic>[
lint(696, 9),
lint(781, 9),
lint(970, 15),
lint(1207, 19),
lint(1390, 22),
lint(1615, 30),
lint(1845, 27),
lint(2028, 9),
lint(2162, 17),
lint(2316, 15),
lint(2513, 17),
]);
}
}
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(NoStopwatchesTest);
});
}

View File

@@ -1,70 +0,0 @@
// 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/utilities/package_config_file_builder.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
extension ExternalStopwatchesExtension on PackageConfigFileBuilder {
PackageConfigFileBuilder addExternalStopwatchesPackage(AnalysisRuleTest test) {
add(
name: ExternalStopwatchesPackage._externalStopwatchesPackageName,
rootPath: test.convertPath(ExternalStopwatchesPackage._externalStopwatchesPackageRoot),
);
return this;
}
}
/// Mixin application that allows for `package:meta` imports in tests.
mixin ExternalStopwatchesPackage on AnalysisRuleTest {
static const String _externalStopwatchesPackageName = 'external_stopwatches';
static const String _externalStopwatchesPackageRoot =
'/packages/$_externalStopwatchesPackageName';
@override
void setUp() {
super.setUp();
newFile('$_externalStopwatchesPackageRoot/lib/external_stopwatches.dart', '''
// External Library that creates Stopwatches. This file will not be analyzed but
// its symbols will be imported by tests.
class MyStopwatch implements Stopwatch {
MyStopwatch();
MyStopwatch.create() : this();
@override
Duration get elapsed => throw UnimplementedError();
@override
int get elapsedMicroseconds => throw UnimplementedError();
@override
int get elapsedMilliseconds => throw UnimplementedError();
@override
int get elapsedTicks => throw UnimplementedError();
@override
int get frequency => throw UnimplementedError();
@override
bool get isRunning => throw UnimplementedError();
@override
void reset() {}
@override
void start() {}
@override
void stop() {}
}
final MyStopwatch stopwatch = MyStopwatch.create();
MyStopwatch createMyStopwatch() => MyStopwatch();
Stopwatch createStopwatch() => Stopwatch();
''');
}
}

View File

@@ -1,35 +0,0 @@
// 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/utilities/package_config_file_builder.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
extension MetaPackageConfigExtnesion on PackageConfigFileBuilder {
PackageConfigFileBuilder addMetaPackage(AnalysisRuleTest test) {
add(
name: MetaPackage._metaPackageName,
rootPath: test.convertPath(MetaPackage._metaPackageRoot),
);
return this;
}
}
/// Mixin application that allows for `package:meta` imports in tests.
mixin MetaPackage on AnalysisRuleTest {
static const String _metaPackageName = 'meta';
static const String _metaPackageRoot = '/packages/$_metaPackageName';
@override
void setUp() {
super.setUp();
newFile('$_metaPackageRoot/lib/meta.dart', '''
library meta;
const protected = Object();
const mustCallSuper = Object();
const factory = Object();
const optionalTypeArgs = Object();
''');
}
}

View File

@@ -1,95 +0,0 @@
// 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/utilities/package_config_file_builder.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
extension FlutterWidgetsPackageConfigExtnesion on PackageConfigFileBuilder {
PackageConfigFileBuilder addFlutterWidgetsPackage(AnalysisRuleTest test) {
add(
name: FlutterWidgetsPackage._flutterPackageName,
rootPath: test.convertPath(FlutterWidgetsPackage._flutterPackageRoot),
);
return this;
}
}
/// Mixin application that allows for `package:flutter/widgets.dart` imports in tests.
mixin FlutterWidgetsPackage on AnalysisRuleTest {
static const String _flutterPackageName = 'flutter';
static const String _flutterPackageRoot = '/packages/$_flutterPackageName';
@override
void setUp() {
super.setUp();
newFile('$_flutterPackageRoot/lib/widgets.dart', '''
library widgets;
abstract class StatefulWidget {
const StatefulWidget();
@protected
@factory
State createState();
}
mixin Diagnosticable {
@protected
@mustCallSuper
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}
class DiagnosticPropertiesBuilder {}
class BuildContext {}
class Widget {}
typedef VoidCallback = void Function();
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
@protected
@mustCallSuper
void initState() {}
@mustCallSuper
@protected
void didUpdateWidget(covariant T oldWidget) {}
@protected
@mustCallSuper
void reassemble() {}
@protected
void setState(VoidCallback fn) {}
@protected
@mustCallSuper
void deactivate() {}
@protected
@mustCallSuper
void activate() {}
@protected
@mustCallSuper
void dispose() {}
@protected
Widget build(BuildContext context);
@protected
@mustCallSuper
void didChangeDependencies() {}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
}
// If @protected State methods are added or removed, the analysis rule should be
// updated accordingly (dev/bots/custom_rules/protect_public_state_subtypes.dart)
}
''');
}
}

View File

@@ -1,171 +0,0 @@
// 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/src/lint/registry.dart';
import 'package:analyzer/utilities/package_config_file_builder.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:flutter_analyzer_plugin/src/rules/protect_public_state_subtypes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package_mixins/meta_mixin.dart';
import 'package_mixins/widgets_mixin.dart';
@reflectiveTest
class ProtectPublicStateSubtypesTest extends AnalysisRuleTest
with MetaPackage, FlutterWidgetsPackage {
@override
void setUp() {
Registry.ruleRegistry.registerWarningRule(ProtectPublicStateSubtypes());
super.setUp();
writeTestPackageConfig(
PackageConfigFileBuilder()
..addFlutterWidgetsPackage(this)
..addMetaPackage(this),
);
}
@override
String get analysisRule => ProtectPublicStateSubtypes.code.name;
static const String source = '''
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
class MyWidget extends StatefulWidget {
@override
State createState() => MyWidgetStateBad();
}
class MyWidgetStateBad extends State<MyWidget>{
@override
void initState() { // ERROR
super.initState();
}
@override
void didUpdateWidget(covariant MyWidget oldWidget) { // ERROR
super.didUpdateWidget(oldWidget);
}
@override
void reassemble() { // ERROR
super.reassemble();
}
@override
void setState(VoidCallback fn) {} // ERROR
@override
void deactivate() { // ERROR
super.deactivate();
}
@override
void activate() { // ERROR
super.activate();
}
@override
void dispose() { // ERROR
super.dispose();
}
@override
Widget build(BuildContext context) => Widget();
@override
void didChangeDependencies() { // ERROR
super.didChangeDependencies();
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { // ERROR
super.debugFillProperties(properties);
}
}
class MyWidgetStateValid extends State<MyWidget>{
@override
@protected
void initState() {
super.initState();
}
@override
@protected
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
}
@override
@protected
void reassemble() {
super.reassemble();
}
@override
@protected
void setState(VoidCallback fn) {}
@override
@protected
void deactivate() {
super.deactivate();
}
@override
@protected
void activate() {
super.activate();
}
@override
@protected
void dispose() {
super.dispose();
}
@override
@protected
Widget build(BuildContext context) => Widget();
@override
@protected
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
@protected
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
}
}
''';
// ignore: non_constant_identifier_names
Future<void> test_protect_public_state_subtypes() async {
await assertDiagnostics(source, <ExpectedDiagnostic>[
lint(224, 66),
lint(294, 115),
lint(413, 68),
lint(485, 45),
lint(543, 68),
lint(615, 64),
lint(683, 62),
lint(749, 59),
lint(812, 90),
lint(906, 134),
]);
}
}
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ProtectPublicStateSubtypesTest);
});
}

View File

@@ -1,104 +0,0 @@
// 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/src/lint/registry.dart';
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:flutter_analyzer_plugin/src/rules/render_box_intrinsics.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@reflectiveTest
class RenderBoxIntrinsicCalculationRuleTest extends AnalysisRuleTest {
@override
void setUp() {
Registry.ruleRegistry.registerWarningRule(RenderBoxIntrinsicCalculationRule());
super.setUp();
}
@override
String get analysisRule => RenderBoxIntrinsicCalculationRule.code.name;
static const String source = '''
// 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.
abstract class RenderBox {
void computeDryBaseline() {}
void computeDryLayout() {}
void computeDistanceToActualBaseline() {}
void computeMaxIntrinsicHeight() {}
void computeMinIntrinsicHeight() {}
void computeMaxIntrinsicWidth() {}
void computeMinIntrinsicWidth() {}
}
mixin ARenderBoxMixin on RenderBox {
@override
void computeMaxIntrinsicWidth() {}
@override
void computeMinIntrinsicWidth() => computeMaxIntrinsicWidth(); // ERROR: computeMaxIntrinsicWidth(). Consider calling getMaxIntrinsicWidth instead.
@override
void computeMinIntrinsicHeight() {
final void Function() f =
computeMaxIntrinsicWidth; // ERROR: f = computeMaxIntrinsicWidth. Consider calling getMaxIntrinsicWidth instead.
f();
}
}
extension ARenderBoxExtension on RenderBox {
void test() {
computeDryBaseline(); // ERROR: computeDryBaseline(). Consider calling getDryBaseline instead.
computeDryLayout(); // ERROR: computeDryLayout(). Consider calling getDryLayout instead.
}
}
class RenderBoxSubclass1 extends RenderBox {
@override
void computeDryLayout() {
computeDistanceToActualBaseline(); // ERROR: computeDistanceToActualBaseline(). Consider calling getDistanceToBaseline, or getDistanceToActualBaseline instead.
}
@override
void computeDistanceToActualBaseline() {
computeMaxIntrinsicHeight(); // ERROR: computeMaxIntrinsicHeight(). Consider calling getMaxIntrinsicHeight instead.
}
/// [RenderBox.computeDryLayout]: // OK
double? getDryBaseline() {
return 0;
}
}
class RenderBoxSubclass2 extends RenderBox with ARenderBoxMixin {
@override
void computeMaxIntrinsicWidth() {
super.computeMinIntrinsicHeight(); // OK
super.computeMaxIntrinsicWidth(); // OK
final void Function() f = super.computeDryBaseline; // OK
f();
}
}
''';
// ignore: non_constant_identifier_names
Future<void> test_render_box_intrinsics() async {
await assertDiagnostics(source, <ExpectedDiagnostic>[
lint(585, 24),
lint(786, 24),
lint(980, 18),
lint(1079, 16),
lint(1264, 31),
lint(1488, 25),
]);
}
}
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(RenderBoxIntrinsicCalculationRuleTest);
});
}

View File

@@ -1,14 +1,5 @@
include: ../analysis_options.yaml
plugins:
flutter_analyzer_plugin:
path: ../../dev/flutter_analyzer_plugin
diagnostics:
no_double_clamp: true
no_stopwatches: true
protect_public_state_subtypes: true
render_box_intrinsics: true
linter:
rules:
# diagnostic_describe_all_properties: true # blocked on https://github.com/dart-lang/sdk/issues/47418

View File

@@ -1,11 +1,5 @@
include: ../analysis_options.yaml
plugins:
flutter_analyzer_plugin:
path: ../dev/flutter_analyzer_plugin
diagnostics:
no_stopwatches: true
linter:
rules:
# Tests try to throw and catch things in exciting ways all the time, so

View File

@@ -1,11 +1,5 @@
include: ../analysis_options.yaml
plugins:
flutter_analyzer_plugin:
path: ../../dev/flutter_analyzer_plugin
diagnostics:
avoid_future_catch_error: true
linter:
rules:
avoid_catches_without_on_clauses: true

View File

@@ -25,14 +25,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.7"
analysis_server_plugin:
dependency: transitive
description:
name: analysis_server_plugin
sha256: eec9e58ac77595118203d6b87ce8ef559eba413f95beea78aea42828b86cf610
url: "https://pub.dev"
source: hosted
version: "0.3.1"
analyzer:
dependency: "direct main"
description:
@@ -41,22 +33,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.0"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: "7c56a95eebc84db60b6dfa5a545e09df95f2ffc4453a3ef2cec59a08c7d70e56"
url: "https://pub.dev"
source: hosted
version: "0.13.8"
analyzer_testing:
dependency: transitive
description:
name: analyzer_testing
sha256: f0c4eb2ffcbe8dfb56e8537de2cff9cad432d1d8644566427148819cf057a35a
url: "https://pub.dev"
source: hosted
version: "0.1.3"
animations:
dependency: "direct main"
description:
@@ -871,14 +847,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.14"
test_reflective_loader:
dependency: transitive
description:
name: test_reflective_loader
sha256: b94229c9ae2a9ebe77d7abfee30641842e7e427b8bd851709de517970b905096
url: "https://pub.dev"
source: hosted
version: "0.3.0"
typed_data:
dependency: "direct main"
description:

View File

@@ -5,7 +5,6 @@ environment:
workspace:
- dev/a11y_assessments
- dev/flutter_analyzer_plugin
- dev/automated_tests
- dev/benchmarks/complex_layout
- dev/benchmarks/imitation_game_flutter