[linter] Adds new switch_on_type lint
Fixes: https://github.com/dart-lang/sdk/issues/59546 Change-Id: Ib78714e03c1487376c961214910d10c24ec9f8ff Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/413600 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Auto-Submit: Felipe Morschel <git@fmorschel.dev>
This commit is contained in:
@@ -2394,6 +2394,8 @@ LintCode.strict_top_level_inference_split_to_types:
|
||||
Conceivably for variable declarations we can search some space (like the
|
||||
library or the compilation unit) for what values are assigned, and suggest
|
||||
the LUB of those value(s)' type(s).
|
||||
LintCode.switch_on_type:
|
||||
status: noFix
|
||||
LintCode.test_types_in_equals:
|
||||
status: noFix
|
||||
LintCode.throw_in_finally:
|
||||
|
||||
@@ -172,6 +172,7 @@ linter:
|
||||
- specify_nonobvious_local_variable_types
|
||||
- specify_nonobvious_property_types
|
||||
- strict_top_level_inference
|
||||
- switch_on_type
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
- tighten_type_of_initializing_formals
|
||||
|
||||
@@ -1542,6 +1542,12 @@ class LinterLintCode extends LintCode {
|
||||
uniqueName: 'strict_top_level_inference_split_to_types',
|
||||
);
|
||||
|
||||
static const LintCode switch_on_type = LinterLintCode(
|
||||
LintNames.switch_on_type,
|
||||
"Avoid switch statements on a 'Type'.",
|
||||
correctionMessage: "Try using pattern matching on a variable instead.",
|
||||
);
|
||||
|
||||
static const LintCode test_types_in_equals = LinterLintCode(
|
||||
LintNames.test_types_in_equals,
|
||||
"Missing type test for '{0}' in '=='.",
|
||||
|
||||
@@ -461,6 +461,8 @@ abstract final class LintNames {
|
||||
|
||||
static const String super_goes_last = 'super_goes_last';
|
||||
|
||||
static const String switch_on_type = 'switch_on_type';
|
||||
|
||||
static const String test_types_in_equals = 'test_types_in_equals';
|
||||
|
||||
static const String throw_in_finally = 'throw_in_finally';
|
||||
|
||||
@@ -190,6 +190,7 @@ import 'rules/specify_nonobvious_local_variable_types.dart';
|
||||
import 'rules/specify_nonobvious_property_types.dart';
|
||||
import 'rules/strict_top_level_inference.dart';
|
||||
import 'rules/super_goes_last.dart';
|
||||
import 'rules/switch_on_type.dart';
|
||||
import 'rules/test_types_in_equals.dart';
|
||||
import 'rules/throw_in_finally.dart';
|
||||
import 'rules/tighten_type_of_initializing_formals.dart';
|
||||
@@ -442,6 +443,7 @@ void registerLintRules() {
|
||||
..registerLintRule(SpecifyNonObviousLocalVariableTypes())
|
||||
..registerLintRule(SpecifyNonObviousPropertyTypes())
|
||||
..registerLintRule(StrictTopLevelInference())
|
||||
..registerLintRule(SwitchOnType())
|
||||
..registerLintRule(TestTypesInEquals())
|
||||
..registerLintRule(ThrowInFinally())
|
||||
..registerLintRule(TightenTypeOfInitializingFormals())
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. 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/features.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';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../extensions.dart';
|
||||
|
||||
const _desc = "Avoid switch statements on a 'Type'.";
|
||||
|
||||
const _objectToStringName = 'toString';
|
||||
|
||||
class SwitchOnType extends LintRule {
|
||||
SwitchOnType() : super(name: LintNames.switch_on_type, description: _desc);
|
||||
|
||||
@override
|
||||
DiagnosticCode get diagnosticCode => LinterLintCode.switch_on_type;
|
||||
|
||||
@override
|
||||
LintCode get lintCode => LinterLintCode.switch_on_type;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
NodeLintRegistry registry,
|
||||
LinterContext context,
|
||||
) {
|
||||
if (!context.isEnabled(Feature.patterns)) return;
|
||||
var visitor = _Visitor(this, context);
|
||||
registry.addSwitchExpression(this, visitor);
|
||||
registry.addSwitchStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final LintRule rule;
|
||||
|
||||
final LinterContext context;
|
||||
|
||||
/// The node where the lint will be reported.
|
||||
late AstNode node;
|
||||
|
||||
_Visitor(this.rule, this.context);
|
||||
|
||||
/// A reference to the [Type] type.
|
||||
///
|
||||
/// This is used to check if the type of the expression is assignable to
|
||||
/// [Type].
|
||||
///
|
||||
/// This shortens the code and avoids multiple calls to
|
||||
/// `context.typeProvider.typeType`.
|
||||
InterfaceType get _typeType => context.typeProvider.typeType;
|
||||
|
||||
@override
|
||||
void visitSwitchExpression(SwitchExpression node) {
|
||||
this.node = node.expression;
|
||||
_processExpression(node.expression);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSwitchStatement(SwitchStatement node) {
|
||||
this.node = node.expression;
|
||||
_processExpression(node.expression);
|
||||
}
|
||||
|
||||
/// Returns `true` if the [type] is assignable to [Type].
|
||||
bool _isAssignableToType(DartType? type) {
|
||||
if (type == null) return false;
|
||||
return context.typeSystem.isAssignableTo(type, _typeType);
|
||||
}
|
||||
|
||||
/// Processes the [expression] of a [SwitchStatement] or [SwitchExpression].
|
||||
///
|
||||
/// Returns `true` if the lint was reportred and `false` otherwise.
|
||||
bool _processExpression(Expression expression) {
|
||||
if (expression case StringInterpolation(:var elements)) {
|
||||
return _processInterpolation(elements);
|
||||
}
|
||||
if (expression case ConditionalExpression(
|
||||
:var thenExpression,
|
||||
:var elseExpression,
|
||||
)) {
|
||||
return _processExpression(thenExpression) ||
|
||||
_processExpression(elseExpression);
|
||||
}
|
||||
if (expression case SwitchExpression(:var cases)) {
|
||||
for (var caseClause in cases) {
|
||||
if (_processExpression(caseClause.expression)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (expression case BinaryExpression(
|
||||
:var leftOperand,
|
||||
:var rightOperand,
|
||||
:var operator,
|
||||
) when operator.lexeme == TokenType.PLUS.lexeme) {
|
||||
return _processExpression(leftOperand) ||
|
||||
_processExpression(rightOperand);
|
||||
}
|
||||
var type = switch (expression) {
|
||||
PrefixedIdentifier(:var identifier) => identifier.staticType,
|
||||
PropertyAccess(:var propertyName) => propertyName.staticType,
|
||||
SimpleIdentifier(:var staticType) => staticType,
|
||||
MethodInvocation(:var methodName, :var realTarget?) =>
|
||||
methodName.element.isToStringMethod ? realTarget.staticType : null,
|
||||
_ => null,
|
||||
};
|
||||
return _reportIfAssignableToType(type);
|
||||
}
|
||||
|
||||
/// Processes the [elements] of an [InterpolationExpression].
|
||||
///
|
||||
/// Returns `true` if the lint was reported and `false` otherwise.
|
||||
bool _processInterpolation(NodeList<InterpolationElement> elements) {
|
||||
for (var element in elements) {
|
||||
switch (element) {
|
||||
case InterpolationExpression(:var expression):
|
||||
var reported = _processExpression(expression);
|
||||
|
||||
// This return is necessary to avoid multiple reporting of the lint
|
||||
if (reported) {
|
||||
return true;
|
||||
}
|
||||
case InterpolationString():
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Reports the lint if the [type] is assignable to [Type].
|
||||
///
|
||||
/// Returns `true` if the lint was reported and `false` otherwise.
|
||||
bool _reportIfAssignableToType(DartType? type) {
|
||||
var reported = false;
|
||||
if (_isAssignableToType(type)) {
|
||||
rule.reportAtNode(node);
|
||||
reported = true;
|
||||
}
|
||||
return reported;
|
||||
}
|
||||
}
|
||||
|
||||
extension on Element? {
|
||||
/// Returns `true` if this element is the `toString` method.
|
||||
bool get isToStringMethod {
|
||||
var self = this;
|
||||
return self is MethodElement && self.name3 == _objectToStringName;
|
||||
}
|
||||
}
|
||||
@@ -11344,6 +11344,82 @@ LintCode:
|
||||
: _children = children,
|
||||
super(style) {
|
||||
```
|
||||
switch_on_type:
|
||||
problemMessage: "Avoid switch statements on a 'Type'."
|
||||
correctionMessage: "Try using pattern matching on a variable instead."
|
||||
state:
|
||||
stable: "3.0"
|
||||
categories: [unintentional, style, languageFeatureUsage, errorProne]
|
||||
hasPublishedDocs: false
|
||||
documentation: |-
|
||||
#### Description
|
||||
|
||||
The analyzer produces this diagnostic when a switch statement or switch
|
||||
expression is used on either the value of a `Type` or a `toString` call
|
||||
on a `Type`.
|
||||
|
||||
#### Example
|
||||
|
||||
The following code produces this diagnostic because the switch statement
|
||||
is used on a `Type`:
|
||||
|
||||
```dart
|
||||
void f(Object o) {
|
||||
switch ([!o.runtimeType!]) {
|
||||
case const (int):
|
||||
print('int');
|
||||
case const (String):
|
||||
print('String');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Common fixes
|
||||
|
||||
Use pattern matching on the variable instead:
|
||||
|
||||
```dart
|
||||
void f(Object o) {
|
||||
switch (o) {
|
||||
case int():
|
||||
print('int');
|
||||
case String():
|
||||
print('String');
|
||||
}
|
||||
}
|
||||
```
|
||||
deprecatedDetails: |-
|
||||
**AVOID** using switch on `Type`.
|
||||
|
||||
Switching on `Type` is not type-safe and can lead to bugs if the
|
||||
class hierarchy changes. Prefer to use pattern matching on the variable
|
||||
instead.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void f(Object o) {
|
||||
switch (o.runtimeType) {
|
||||
case int:
|
||||
print('int');
|
||||
case String:
|
||||
print('String');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void f(Object o) {
|
||||
switch(o) {
|
||||
case int():
|
||||
print('int');
|
||||
case String _:
|
||||
print('String');
|
||||
default:
|
||||
print('other');
|
||||
}
|
||||
}
|
||||
```
|
||||
test_types_in_equals:
|
||||
problemMessage: "Missing type test for '{0}' in '=='."
|
||||
correctionMessage: "Try testing the type of '{0}'."
|
||||
|
||||
@@ -242,6 +242,7 @@ import 'specify_nonobvious_local_variable_types_test.dart'
|
||||
import 'specify_nonobvious_property_types_test.dart'
|
||||
as specify_nonobvious_property_types;
|
||||
import 'strict_top_level_inference_test.dart' as strict_top_level_inference;
|
||||
import 'switch_on_type_test.dart' as switch_on_type;
|
||||
import 'test_types_in_equals_test.dart' as test_types_in_equals;
|
||||
import 'throw_in_finally_test.dart' as throw_in_finally;
|
||||
import 'tighten_type_of_initializing_formals_test.dart'
|
||||
@@ -502,6 +503,7 @@ void main() {
|
||||
specify_nonobvious_local_variable_types.main();
|
||||
specify_nonobvious_property_types.main();
|
||||
strict_top_level_inference.main();
|
||||
switch_on_type.main();
|
||||
test_types_in_equals.main();
|
||||
throw_in_finally.main();
|
||||
tighten_type_of_initializing_formals.main();
|
||||
|
||||
@@ -0,0 +1,892 @@
|
||||
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. 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:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import '../rule_test_support.dart';
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(SwitchExpressionOnTypeTest);
|
||||
defineReflectiveTests(SwitchStatementOnTypeTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class SwitchExpressionOnTypeTest extends LintRuleTest {
|
||||
@override
|
||||
String get lintRule => LintNames.switch_on_type;
|
||||
|
||||
Future<void> test_binaryExpression() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('' + '$t') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 9)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_conditionalBoth() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch (1 == 1 ? t : '$t') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 17)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_conditionalElse() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch (1 == 1 ? 'other' : '$t') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 23)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_functionToString() async {
|
||||
await assertNoDiagnostics('''
|
||||
void f() {
|
||||
(switch (toString()) {
|
||||
'int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
|
||||
String toString() => '';
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_functionToString_prefixed() async {
|
||||
await assertNoDiagnostics('''
|
||||
import '' as self;
|
||||
|
||||
void f() {
|
||||
(switch (self.toString()) {
|
||||
'int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
|
||||
String toString() => '';
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_insideClass_implicitThis() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (runtimeType) {
|
||||
const (A) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(36, 11)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_insideClass_withThis() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (this.runtimeType) {
|
||||
const (A) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(36, 16)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_nestedSwitch() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t, Object o) {
|
||||
(switch (switch(o) {_ => t}) {
|
||||
const (int) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(38, 18)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_other() async {
|
||||
await assertNoDiagnostics('''
|
||||
void f(num i) {
|
||||
(switch (i) {
|
||||
int _ => null,
|
||||
double _ => null,
|
||||
});
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_override() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(MyClass i) {
|
||||
(switch (i.runtimeType) {
|
||||
const (MyClass) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
|
||||
class MyClass {
|
||||
@override
|
||||
Type get runtimeType => int;
|
||||
}
|
||||
''',
|
||||
[lint(31, 13)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeType() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(num i) {
|
||||
(switch (i.runtimeType) {
|
||||
const (int) => null,
|
||||
const (double) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(27, 13)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(num n) {
|
||||
(switch (n.runtimeType.toString()) {
|
||||
'int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(27, 24)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_insideClass() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (runtimeType.toString()) {
|
||||
'A' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(36, 22)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_insideClass_override() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (runtimeType.toString()) {
|
||||
'A' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
MyType get runtimeType => const MyType();
|
||||
}
|
||||
|
||||
class MyType implements Type {
|
||||
const MyType();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MyType';
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(36, 22)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_noCall() async {
|
||||
await assertNoDiagnostics('''
|
||||
void f(num n) {
|
||||
(switch (n.runtimeType.toString) {
|
||||
function => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
|
||||
void function() {}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_noCall_insideClass() async {
|
||||
await assertNoDiagnostics('''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (runtimeType.toString) {
|
||||
function => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void function() {}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_stringAddition() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('type: ' + t.toString()) {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 23)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('type: $t') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 10)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerConditionalResult() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('type: ${1 == 1 ? '$t' : 'other'}') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 34)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerInterpolation() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('type: ${'inner string $t'}') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 28)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerSwitchResult() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('type: ${switch (1) {_ => '$t',}}') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 34)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerTest() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
(switch ('type: ${t == int ? 'int' : '$t'}') {
|
||||
'type: int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 34)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_toString_insideClass_implicitThis() async {
|
||||
await assertNoDiagnostics('''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (toString()) {
|
||||
'A' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_toString_insideClass_withThis() async {
|
||||
await assertNoDiagnostics('''
|
||||
class A {
|
||||
void m() {
|
||||
(switch (this.toString()) {
|
||||
'A' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_typeParameter() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f<T>() {
|
||||
(switch (T) {
|
||||
const (int) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(25, 1)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_variable_typeToString() async {
|
||||
await assertNoDiagnostics(r'''
|
||||
void f(Object? o) {
|
||||
final type = o.runtimeType.toString();
|
||||
(switch (type) {
|
||||
'int' => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_variableType() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(Type t) {
|
||||
(switch (t) {
|
||||
const (int) => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
''',
|
||||
[lint(28, 1)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class SwitchStatementOnTypeTest extends LintRuleTest {
|
||||
@override
|
||||
String get lintRule => LintNames.switch_on_type;
|
||||
|
||||
Future<void> test_binaryExpression() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('' + '$t') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 9)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_conditionalBoth() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch (1 == 1 ? t : '$t') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 17)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_conditionalElse() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch (1 == 1 ? 'other' : '$t') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 23)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_functionToString() async {
|
||||
await assertNoDiagnostics('''
|
||||
void f() {
|
||||
switch (toString()) {
|
||||
case 'int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String toString() => '';
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_functionToString_prefixed() async {
|
||||
await assertNoDiagnostics('''
|
||||
import '' as self;
|
||||
|
||||
void f() {
|
||||
switch (self.toString()) {
|
||||
case 'int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String toString() => '';
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_insideClass_implicitThis() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
switch (runtimeType) {
|
||||
case const (A):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(35, 11)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_insideClass_withThis() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
switch (this.runtimeType) {
|
||||
case const (A):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(35, 16)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_nestedSwitch() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t, Object o) {
|
||||
switch (switch(o) {_ => t}) {
|
||||
case const (int):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(37, 18)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_other() async {
|
||||
await assertNoDiagnostics('''
|
||||
void f(num i) {
|
||||
switch (i) {
|
||||
case int _:
|
||||
break;
|
||||
case double _:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_override() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(MyClass i) {
|
||||
switch (i.runtimeType) {
|
||||
case const (MyClass):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class MyClass {
|
||||
@override
|
||||
Type get runtimeType => int;
|
||||
}
|
||||
''',
|
||||
[lint(30, 13)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_prePatterns() async {
|
||||
await assertNoDiagnostics('''
|
||||
// @dart = 2.19
|
||||
|
||||
void f(num i) {
|
||||
switch (i.runtimeType) {
|
||||
case int:
|
||||
break;
|
||||
case double:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_runtimeType() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(num i) {
|
||||
switch (i.runtimeType) {
|
||||
case const (int):
|
||||
break;
|
||||
case const (double):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(26, 13)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(num n) {
|
||||
switch (n.runtimeType.toString()) {
|
||||
case 'int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(26, 24)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_insideClass() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
switch (runtimeType.toString()) {
|
||||
case 'A':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(35, 22)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_insideClass_override() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
class A {
|
||||
void m() {
|
||||
switch (runtimeType.toString()) {
|
||||
case 'A':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
MyType get runtimeType => const MyType();
|
||||
}
|
||||
|
||||
class MyType implements Type {
|
||||
const MyType();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MyType';
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(35, 22)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_noCall() async {
|
||||
await assertNoDiagnostics('''
|
||||
void f(num n) {
|
||||
switch (n.runtimeType.toString) {
|
||||
case function:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void function() {}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_runtimeTypeToString_noCall_insideClass() async {
|
||||
await assertNoDiagnostics('''
|
||||
class A {
|
||||
void m() {
|
||||
switch (runtimeType.toString) {
|
||||
case function:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void function() {}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_stringAddition() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('type: ' + t.toString()) {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 23)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('type: $t') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 10)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerConditionalResult() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('type: ${1 == 1 ? '$t' : 'other'}') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 34)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerInterpolation() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('type: ${'inner string $t'}') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 28)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerSwitchResult() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('type: ${switch (1) {_ => '$t',}}') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 34)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_innerTest() async {
|
||||
await assertDiagnostics(
|
||||
r'''
|
||||
void f(Type t) {
|
||||
switch ('type: ${t == int ? 'int' : '$t'}') {
|
||||
case 'type: int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 34)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_toString_insideClass_implicitThis() async {
|
||||
await assertNoDiagnostics('''
|
||||
class A {
|
||||
void m() {
|
||||
switch (toString()) {
|
||||
case 'A':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_toString_insideClass_withThis() async {
|
||||
await assertNoDiagnostics('''
|
||||
class A {
|
||||
void m() {
|
||||
switch (this.toString()) {
|
||||
case 'A':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_typeParameter() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f<T>() {
|
||||
switch (T) {
|
||||
case const (int):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(24, 1)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_variable_typeToString() async {
|
||||
await assertNoDiagnostics(r'''
|
||||
void f(Object? o) {
|
||||
final type = o.runtimeType.toString();
|
||||
switch (type) {
|
||||
case 'int':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_variableType() async {
|
||||
await assertDiagnostics(
|
||||
'''
|
||||
void f(Type t) {
|
||||
switch (t) {
|
||||
case const (int):
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
''',
|
||||
[lint(27, 1)],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user