linter: Introduce no_dynamic_casts replacing strict-casts
Work towards https://github.com/dart-lang/sdk/issues/63527 We will want to deprecate the `analyzer/language/strict-casts` setting, but we first need to ship an SDK to Flutter that offers the lint rule, before we deprecate the setting, which will cause CI to fail (like a Dart->Flutter roll). When the deprecation is enabled, we can also ship the automated fix. Change-Id: I0e9651171b721577acbd416d254bca3d0324f3f9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509521 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
committed by
Samuel Rawlins
parent
56e0c9fca3
commit
d33ce2f88d
@@ -120,6 +120,8 @@ To learn more about the feature, check out the
|
||||
|
||||
- A `no_raw_types` lint rule is introduced, which replaces the
|
||||
`strict-raw-types` analysis option, offering a more consistent approach.
|
||||
- A `no_dynamic_types` lint rule is introduced, which replaces the
|
||||
`strict-casts` analysis option, offering a more consistent approach.
|
||||
- The following lint rules have been determined to be low value, and are
|
||||
deprecated: `avoid_public_typedef_functions`, and `one_member_abstracts`.
|
||||
If there is desire to keep using these, they can be re-implemented with
|
||||
|
||||
@@ -2282,6 +2282,9 @@ no_default_cases:
|
||||
values that the default would have matched.
|
||||
no_duplicate_case_values:
|
||||
status: hasFix
|
||||
no_dynamic_casts:
|
||||
status: needsFix
|
||||
notes: The easy fix is to wrap the expression in a cast.
|
||||
no_leading_underscores_for_library_prefixes:
|
||||
status: hasFix
|
||||
no_leading_underscores_for_local_identifiers:
|
||||
|
||||
@@ -1851,6 +1851,15 @@ noDuplicateCaseValues = LinterLintTemplate(
|
||||
expectedTypes: [ExpectedType.object, ExpectedType.object],
|
||||
);
|
||||
|
||||
/// No parameters.
|
||||
const LinterLintWithoutArguments noDynamicCasts = LinterLintWithoutArguments(
|
||||
name: 'no_dynamic_casts',
|
||||
problemMessage: "Implicit cast from 'dynamic'.",
|
||||
correctionMessage: "Try adding an explicit cast or changing the target type.",
|
||||
uniqueName: 'no_dynamic_casts',
|
||||
expectedTypes: [],
|
||||
);
|
||||
|
||||
/// Parameters:
|
||||
/// Object p0: undocumented
|
||||
const DiagnosticWithArguments<
|
||||
|
||||
@@ -306,6 +306,8 @@ abstract final class LintNames {
|
||||
|
||||
static const String no_duplicate_case_values = 'no_duplicate_case_values';
|
||||
|
||||
static const String no_dynamic_casts = 'no_dynamic_casts';
|
||||
|
||||
static const String no_leading_underscores_for_library_prefixes =
|
||||
'no_leading_underscores_for_library_prefixes';
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ import 'rules/missing_whitespace_between_adjacent_strings.dart';
|
||||
import 'rules/no_adjacent_strings_in_list.dart';
|
||||
import 'rules/no_default_cases.dart';
|
||||
import 'rules/no_duplicate_case_values.dart';
|
||||
import 'rules/no_dynamic_casts.dart';
|
||||
import 'rules/no_leading_underscores_for_library_prefixes.dart';
|
||||
import 'rules/no_leading_underscores_for_local_identifiers.dart';
|
||||
import 'rules/no_literal_bool_comparisons.dart';
|
||||
@@ -382,6 +383,7 @@ void registerLintRules() {
|
||||
..registerLintRule(NoAdjacentStringsInList())
|
||||
..registerLintRule(NoDefaultCases())
|
||||
..registerLintRule(NoDuplicateCaseValues())
|
||||
..registerLintRule(NoDynamicCasts())
|
||||
..registerLintRule(NoLeadingUnderscoresForLibraryPrefixes())
|
||||
..registerLintRule(NoLeadingUnderscoresForLocalIdentifiers())
|
||||
..registerLintRule(NoLiteralBoolComparisons())
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
// Copyright (c) 2026, 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/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/type.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
import '../diagnostic.dart' as diag;
|
||||
|
||||
const _desc = r'Avoid implicit casts from `dynamic`.';
|
||||
|
||||
class NoDynamicCasts extends AnalysisRule {
|
||||
new() : super(name: LintNames.no_dynamic_casts, description: _desc);
|
||||
|
||||
@override
|
||||
DiagnosticCode get diagnosticCode => diag.noDynamicCasts;
|
||||
|
||||
@override
|
||||
void registerNodeProcessors(
|
||||
RuleVisitorRegistry registry,
|
||||
RuleContext context,
|
||||
) {
|
||||
var visitor = _Visitor(this, context);
|
||||
registry
|
||||
..addArgumentList(this, visitor)
|
||||
..addAssignmentExpression(this, visitor)
|
||||
..addBinaryExpression(this, visitor)
|
||||
..addConditionalExpression(this, visitor)
|
||||
..addDoStatement(this, visitor)
|
||||
..addExpressionFunctionBody(this, visitor)
|
||||
..addForEachPartsWithDeclaration(this, visitor)
|
||||
..addForEachPartsWithIdentifier(this, visitor)
|
||||
..addForEachPartsWithPattern(this, visitor)
|
||||
..addForStatement(this, visitor)
|
||||
..addIfElement(this, visitor)
|
||||
..addIfStatement(this, visitor)
|
||||
..addListLiteral(this, visitor)
|
||||
..addPrefixExpression(this, visitor)
|
||||
..addReturnStatement(this, visitor)
|
||||
..addSetOrMapLiteral(this, visitor)
|
||||
..addVariableDeclaration(this, visitor)
|
||||
..addWhenClause(this, visitor)
|
||||
..addWhileStatement(this, visitor)
|
||||
..addYieldStatement(this, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor extends SimpleAstVisitor<void> {
|
||||
final AnalysisRule _rule;
|
||||
final RuleContext _context;
|
||||
|
||||
new(this._rule, this._context);
|
||||
|
||||
@override
|
||||
void visitArgumentList(ArgumentList node) {
|
||||
for (var argument in node.arguments) {
|
||||
_check(
|
||||
argument.argumentExpression,
|
||||
argument.correspondingParameter?.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitAssignmentExpression(AssignmentExpression node) {
|
||||
_check(node.rightHandSide, node.writeType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBinaryExpression(BinaryExpression node) {
|
||||
if (node.operator.type == TokenType.AMPERSAND_AMPERSAND ||
|
||||
node.operator.type == TokenType.BAR_BAR) {
|
||||
_check(node.leftOperand, _context.typeProvider.boolType);
|
||||
_check(node.rightOperand, _context.typeProvider.boolType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConditionalExpression(ConditionalExpression node) {
|
||||
_check(node.condition, _context.typeProvider.boolType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDoStatement(DoStatement node) {
|
||||
_check(node.condition, _context.typeProvider.boolType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
||||
var returnType = _getEnclosingReturnType(node);
|
||||
_check(node.expression, returnType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
|
||||
_checkForEachParts(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
|
||||
_checkForEachParts(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
|
||||
_checkForEachParts(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForStatement(ForStatement node) {
|
||||
if (node.forLoopParts case ForParts parts) {
|
||||
var condition = parts.condition;
|
||||
if (condition != null) {
|
||||
_check(condition, _context.typeProvider.boolType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIfElement(IfElement node) {
|
||||
_check(node.expression, _context.typeProvider.boolType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIfStatement(IfStatement node) {
|
||||
_check(node.expression, _context.typeProvider.boolType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitListLiteral(ListLiteral node) {
|
||||
var type = node.staticType;
|
||||
if (type case ParameterizedType(typeArguments: [var elementType])) {
|
||||
for (var element in node.elements) {
|
||||
_checkCollectionElement(element, elementType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPrefixExpression(PrefixExpression node) {
|
||||
if (node.operator.type == TokenType.BANG) {
|
||||
_check(node.operand, _context.typeProvider.boolType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitReturnStatement(ReturnStatement node) {
|
||||
if (node.expression case var expression?) {
|
||||
var returnType = _getEnclosingReturnType(node);
|
||||
_check(expression, returnType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSetOrMapLiteral(SetOrMapLiteral node) {
|
||||
var type = node.staticType;
|
||||
if (type is! ParameterizedType) return;
|
||||
if (node.isSet && type.typeArguments.isNotEmpty) {
|
||||
var elementType = type.typeArguments[0];
|
||||
for (var element in node.elements) {
|
||||
_checkCollectionElement(element, elementType);
|
||||
}
|
||||
} else if (node.isMap && type.typeArguments.length == 2) {
|
||||
var keyType = type.typeArguments[0];
|
||||
var valueType = type.typeArguments[1];
|
||||
for (var element in node.elements) {
|
||||
_checkCollectionElementMap(element, keyType, valueType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitVariableDeclaration(VariableDeclaration node) {
|
||||
if (node.initializer case var initializer?) {
|
||||
var parent = node.parent;
|
||||
if (parent case VariableDeclarationList(:var type?)) {
|
||||
_check(initializer, type.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitWhenClause(WhenClause node) {
|
||||
_check(node.expression, _context.typeProvider.boolType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitWhileStatement(WhileStatement node) {
|
||||
_check(node.condition, _context.typeProvider.boolType);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitYieldStatement(YieldStatement node) {
|
||||
var returnType = _getEnclosingReturnType(node);
|
||||
if (returnType == null) return;
|
||||
|
||||
if (node.star != null) {
|
||||
_check(node.expression, returnType);
|
||||
} else {
|
||||
if (returnType case ParameterizedType(typeArguments: [var elementType])) {
|
||||
_check(node.expression, elementType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports lint if [expression] is `dynamic`-typed and [targetType] is
|
||||
/// neither `dynamic` nor `Object?`.
|
||||
void _check(Expression expression, DartType? targetType) {
|
||||
if (targetType == null) return;
|
||||
|
||||
var sourceType = expression.staticType;
|
||||
if (sourceType is! DynamicType) return;
|
||||
|
||||
if (targetType is DynamicType) return;
|
||||
if (targetType == _context.typeProvider.objectQuestionType) return;
|
||||
|
||||
// Ignore if the expression is an explicit cast.
|
||||
if (expression.unParenthesized is AsExpression) return;
|
||||
|
||||
_rule.reportAtNode(expression);
|
||||
}
|
||||
|
||||
/// Checks [element], as an element in a List or Set literal, for
|
||||
/// `dynamic`-typed sub-elements.
|
||||
void _checkCollectionElement(
|
||||
CollectionElement element,
|
||||
DartType elementType,
|
||||
) {
|
||||
switch (element) {
|
||||
case Expression():
|
||||
_check(element, elementType);
|
||||
case IfElement():
|
||||
_checkCollectionElement(element.thenElement, elementType);
|
||||
if (element.elseElement case var elseElement?) {
|
||||
_checkCollectionElement(elseElement, elementType);
|
||||
}
|
||||
case ForElement():
|
||||
_checkCollectionElement(element.body, elementType);
|
||||
case SpreadElement():
|
||||
var expectedType = _context.typeProvider.iterableType(elementType);
|
||||
_check(element.expression, expectedType);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks [element], as an element in a Map literal, for `dynamic`-typed
|
||||
/// sub-elements.
|
||||
void _checkCollectionElementMap(
|
||||
CollectionElement element,
|
||||
DartType keyType,
|
||||
DartType valueType,
|
||||
) {
|
||||
switch (element) {
|
||||
case MapLiteralEntry():
|
||||
_check(element.key, keyType);
|
||||
_check(element.value, valueType);
|
||||
case IfElement():
|
||||
_checkCollectionElementMap(element.thenElement, keyType, valueType);
|
||||
if (element.elseElement case var elseElement?) {
|
||||
_checkCollectionElementMap(elseElement, keyType, valueType);
|
||||
}
|
||||
case ForElement():
|
||||
_checkCollectionElementMap(element.body, keyType, valueType);
|
||||
case SpreadElement():
|
||||
var expectedType = _context.typeProvider.mapType(keyType, valueType);
|
||||
_check(element.expression, expectedType);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks [node] for `dynamic`-typed sub-expressions.
|
||||
void _checkForEachParts(ForEachParts node) {
|
||||
var forStatement = node.parent;
|
||||
if (forStatement is! ForStatement) return;
|
||||
var isAsync = forStatement.awaitKeyword != null;
|
||||
var targetType = isAsync
|
||||
? _context.typeProvider.streamType(_context.typeProvider.dynamicType)
|
||||
: _context.typeProvider.iterableType(_context.typeProvider.dynamicType);
|
||||
_check(node.iterable, targetType);
|
||||
|
||||
// Also check loop variable assignment.
|
||||
DartType? loopVarType;
|
||||
if (node is ForEachPartsWithDeclaration) {
|
||||
loopVarType = node.loopVariable.type?.type;
|
||||
} else if (node is ForEachPartsWithIdentifier) {
|
||||
loopVarType = node.identifier.staticType;
|
||||
}
|
||||
if (loopVarType == null) return;
|
||||
if (loopVarType is DynamicType || loopVarType is VoidType) return;
|
||||
|
||||
var iterableType = node.iterable.staticType;
|
||||
DartType? elementType;
|
||||
if (iterableType is ParameterizedType &&
|
||||
iterableType.typeArguments.isNotEmpty) {
|
||||
elementType = iterableType.typeArguments[0];
|
||||
} else if (iterableType is DynamicType) {
|
||||
elementType = iterableType;
|
||||
}
|
||||
if (elementType is! DynamicType) return;
|
||||
if (targetType is DynamicType) return;
|
||||
if (targetType == _context.typeProvider.objectQuestionType) return;
|
||||
|
||||
_rule.reportAtNode(node.iterable);
|
||||
}
|
||||
|
||||
DartType? _getEnclosingReturnType(AstNode node) {
|
||||
var parent = node.thisOrAncestorMatching(
|
||||
(e) =>
|
||||
e is FunctionDeclaration ||
|
||||
e is MethodDeclaration ||
|
||||
e is ConstructorDeclaration ||
|
||||
e is FunctionExpression,
|
||||
);
|
||||
if (parent == null) return null;
|
||||
|
||||
DartType? returnType;
|
||||
bool isAsync = false;
|
||||
|
||||
if (parent is FunctionDeclaration) {
|
||||
returnType = parent.declaredFragment?.element.returnType;
|
||||
isAsync = parent.functionExpression.body.isAsynchronous;
|
||||
} else if (parent is MethodDeclaration) {
|
||||
returnType = parent.declaredFragment?.element.returnType;
|
||||
isAsync = parent.body.isAsynchronous;
|
||||
} else if (parent is ConstructorDeclaration) {
|
||||
returnType = parent.declaredFragment?.element.returnType;
|
||||
} else if (parent is FunctionExpression) {
|
||||
if (parent.staticType case FunctionType staticType) {
|
||||
returnType = staticType.returnType;
|
||||
}
|
||||
isAsync = parent.body.isAsynchronous;
|
||||
}
|
||||
|
||||
if (returnType == null) return null;
|
||||
|
||||
return isAsync ? _context.typeSystem.flatten(returnType) : returnType;
|
||||
}
|
||||
}
|
||||
@@ -7619,6 +7619,35 @@ LinterLintCode:
|
||||
NOTE: this lint only reports duplicate cases in libraries opted in to Dart 2.19
|
||||
and below. In Dart 3.0 and after, duplicate cases are reported as dead code
|
||||
by the analyzer.
|
||||
noDynamicCasts:
|
||||
type: lint
|
||||
parameters: none
|
||||
problemMessage: "Implicit cast from 'dynamic'."
|
||||
correctionMessage: "Try adding an explicit cast or changing the target type."
|
||||
state:
|
||||
stable: "3.13"
|
||||
categories: [errorProne]
|
||||
hasPublishedDocs: false
|
||||
deprecatedDetails: |-
|
||||
**DO** avoid implicit casts from `dynamic`.
|
||||
|
||||
Assigning a `dynamic`-typed expression to a non-`dynamic`, non-`Object?`
|
||||
target is a form of implicit casting. It is better to make such a cast
|
||||
explicit, so that it is visible to developers.
|
||||
|
||||
**BAD:**
|
||||
```dart
|
||||
void f(dynamic x) {
|
||||
int y = x;
|
||||
}
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```dart
|
||||
void f(dynamic x) {
|
||||
int y = x as int;
|
||||
}
|
||||
```
|
||||
noLeadingUnderscoresForLibraryPrefixes:
|
||||
type: lint
|
||||
parameters:
|
||||
|
||||
@@ -151,6 +151,7 @@ import 'missing_whitespace_between_adjacent_strings_test.dart'
|
||||
import 'no_adjacent_strings_in_list_test.dart' as no_adjacent_strings_in_list;
|
||||
import 'no_default_cases_test.dart' as no_default_cases;
|
||||
import 'no_duplicate_case_values_test.dart' as no_duplicate_case_values;
|
||||
import 'no_dynamic_casts_test.dart' as no_dynamic_casts;
|
||||
import 'no_leading_underscores_for_library_prefixes_test.dart'
|
||||
as no_leading_underscores_for_library_prefixes;
|
||||
import 'no_leading_underscores_for_local_identifiers_test.dart'
|
||||
@@ -451,6 +452,7 @@ void main() {
|
||||
no_adjacent_strings_in_list.main();
|
||||
no_default_cases.main();
|
||||
no_duplicate_case_values.main();
|
||||
no_dynamic_casts.main();
|
||||
no_leading_underscores_for_library_prefixes.main();
|
||||
no_leading_underscores_for_local_identifiers.main();
|
||||
no_literal_bool_comparisons.main();
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
// Copyright (c) 2026, 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(NoDynamicCastsTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class NoDynamicCastsTest extends LintRuleTest {
|
||||
@override
|
||||
String get lintRule => LintNames.no_dynamic_casts;
|
||||
|
||||
test_argument() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(int x) {}
|
||||
void g(dynamic a) {
|
||||
f([!a!]);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_assignment() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
int x = [!a!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_assignment_ok() async {
|
||||
await assertNoDiagnostics(r'''
|
||||
void f(dynamic a) {
|
||||
dynamic x = a;
|
||||
Object? y = a;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_condition_conditionalExpression() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
[!a!] ? 1 : 2;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_condition_doLoop() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
do {} while ([!a!]);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_condition_forLoop() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
for (; [!a!];) {}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_condition_ifExpression() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
[if ([!a!]) 7];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_condition_ifStatement() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
if ([!a!]) {}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_condition_whileLoop() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
while ([!a!]) {}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_explicitCast_ok() async {
|
||||
await assertNoDiagnostics(r'''
|
||||
void f(dynamic a) {
|
||||
int x = a as int;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_expressionFunctionBody() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
int f(dynamic a) => [!a!];
|
||||
''');
|
||||
}
|
||||
|
||||
test_forEach_iterable() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
for (var x in [!a!]) {}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_forEach_variable() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(List<dynamic> list) {
|
||||
for (int x in [!list!]) {}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_listLiteral() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
var list = <int>[[!a!]];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_logicalBinary_left() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a, bool b) {
|
||||
[!a!] && b;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_logicalBinary_right() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(bool a, dynamic b) {
|
||||
a && [!b!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_mapLiteral_key() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
var map = <int, String>{[!a!]: 'x'};
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_mapLiteral_spread() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
var map = <String, int>{...[!a!]};
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_mapLiteral_value() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
var map = <String, int>{'x': [!a!]};
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_namedArgument() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f({required int x}) {}
|
||||
void g(dynamic a) {
|
||||
f(x: [!a!]);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_negation() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
![!a!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_return() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
int f(dynamic a) {
|
||||
return [!a!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_return_async() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
Future<int> f(dynamic a) async {
|
||||
return [!a!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_setLiteral() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
var set = <int>{[!a!]};
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_spreadList() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
void f(dynamic a) {
|
||||
var list = <int>[...[!a!]];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_yield() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
Iterable<int> f(dynamic a) sync* {
|
||||
yield [!a!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_yieldStar() async {
|
||||
await assertDiagnosticsFromMarkdown(r'''
|
||||
Iterable<int> f(dynamic a) sync* {
|
||||
yield* [!a!];
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user