linter: Introduce no_raw_types replacing strict-raw-types

Work towards https://github.com/dart-lang/sdk/issues/63516

I have code here to deprecate the `analyzer/language/strict-raw-types`
setting. But I disabled it, as I realized 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: I17d1ea9aba96063059e37891c05d4a8bd3f02737
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509063
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Sam Rawlins
2026-06-04 14:12:28 -07:00
committed by Samuel Rawlins
parent 2e682b9e9d
commit 4c6fe56ae3
12 changed files with 704 additions and 16 deletions
@@ -2290,6 +2290,11 @@ no_literal_bool_comparisons:
status: hasFix
no_logic_in_create_state:
status: noFix
no_raw_types:
status: needsFix
notes: |-
There are a few basic cases where we can look at another expression's type
in order to find a replacement, like `C c = C<int>()`.
no_runtimetype_tostring:
status: noFix
no_self_assignments:
@@ -812,23 +812,16 @@ class _LanguageOptionValidator extends OptionsValidator {
var language = analyzer.valueAt(AnalysisOptionsFileKeys.language);
if (language is YamlMap) {
language.nodes.forEach((k, v) {
String? key;
bool validKey = false;
if (k is YamlScalar) {
key = k.value?.toString();
if (!AnalysisOptionsFileKeys.languageOptions.contains(key)) {
_builder.reportError(
reporter,
AnalysisOptionsFileKeys.language,
k,
);
} else {
// If we have a valid key, go on and check the value.
validKey = true;
}
if (k is! YamlScalar) return;
var key = k.value?.toString();
if (!AnalysisOptionsFileKeys.languageOptions.contains(key)) {
_builder.reportError(reporter, AnalysisOptionsFileKeys.language, k);
return;
}
if (validKey && v is YamlScalar) {
if (v.toBool() == null) {
if (v is YamlScalar) {
var value = v.toBool();
if (value == null) {
// `null` is not a valid key, so we can safely assume `key` is
// non-`null`.
reporter.report(
@@ -840,6 +833,17 @@ class _LanguageOptionValidator extends OptionsValidator {
)
.atSourceSpan(v.span),
);
} else if (key == AnalysisOptionsFileKeys.strictRawTypes && value) {
// TODO(srawlins): Enable once the `no_raw_types` lint rule is
// available in Flutter main, so that developers have something to
// migrate to.
if (1 == 2) {
reporter.report(
diag.analysisOptionDeprecated
.withArguments(optionName: key!)
.atSourceSpan(k.span),
);
}
}
}
});
@@ -308,6 +308,26 @@ analyzer:
);
}
@FailingTest(reason: 'Enable when we deprecate strict-raw-types')
test_analyzer_language_strictRawTypes_deprecated() {
validate(
'''
analyzer:
language:
strict-raw-types: true
''',
[diag.analysisOptionDeprecated],
);
}
test_analyzer_language_strictRawTypes_notDeprecatedIfFalse() {
validate('''
analyzer:
language:
strict-raw-types: false
''', []);
}
test_analyzer_language_supports_empty() {
validate('''
analyzer:
+2
View File
@@ -2,6 +2,8 @@
// 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/element/type.dart';
import 'analyzer.dart';
import 'diagnostic.dart' as diag;
+19
View File
@@ -1930,6 +1930,21 @@ const LinterLintWithoutArguments noopPrimitiveOperations =
expectedTypes: [],
);
/// Parameters:
/// Type type: the name of the generic type
const DiagnosticWithArguments<
LocatableDiagnostic Function({required DartType type})
>
noRawTypes = LinterLintTemplate(
name: 'no_raw_types',
problemMessage:
"The generic type '{0}' should have explicit type arguments but doesn't.",
correctionMessage: "Use explicit type arguments for '{0}'.",
uniqueName: 'no_raw_types',
withArguments: _withArgumentsNoRawTypes,
expectedTypes: [ExpectedType.type],
);
/// No parameters.
const LinterLintWithoutArguments noRuntimetypeTostring =
LinterLintWithoutArguments(
@@ -4326,6 +4341,10 @@ LocatableDiagnostic _withArgumentsNonConstantIdentifierNames({
return LocatableDiagnosticImpl(diag.nonConstantIdentifierNames, [p0]);
}
LocatableDiagnostic _withArgumentsNoRawTypes({required DartType type}) {
return LocatableDiagnosticImpl(diag.noRawTypes, [type]);
}
LocatableDiagnostic _withArgumentsOneMemberAbstracts({required Object p0}) {
return LocatableDiagnosticImpl(diag.oneMemberAbstracts, [p0]);
}
+2
View File
@@ -317,6 +317,8 @@ abstract final class LintNames {
static const String no_logic_in_create_state = 'no_logic_in_create_state';
static const String no_raw_types = 'no_raw_types';
static const String no_runtimetype_tostring = 'no_runtimetype_tostring';
static const String no_self_assignments = 'no_self_assignments';
+2
View File
@@ -119,6 +119,7 @@ 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';
import 'rules/no_logic_in_create_state.dart';
import 'rules/no_raw_types.dart';
import 'rules/no_runtimeType_toString.dart';
import 'rules/no_self_assignments.dart';
import 'rules/no_wildcard_variable_uses.dart';
@@ -385,6 +386,7 @@ void registerLintRules() {
..registerLintRule(NoLeadingUnderscoresForLocalIdentifiers())
..registerLintRule(NoLiteralBoolComparisons())
..registerLintRule(NoLogicInCreateState())
..registerLintRule(NoRawTypes())
..registerLintRule(NoRuntimeTypeToString())
..registerLintRule(NoSelfAssignments())
..registerLintRule(NoWildcardVariableUses())
@@ -0,0 +1,96 @@
// 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/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 raw types.';
class NoRawTypes extends AnalysisRule {
new() : super(name: LintNames.no_raw_types, description: _desc);
@override
DiagnosticCode get diagnosticCode => diag.noRawTypes;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
var visitor = _Visitor(this);
registry.addNamedType(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final AnalysisRule rule;
new(this.rule);
@override
void visitNamedType(NamedType node) {
var parent = node.parent;
if (parent is ConstructorName &&
parent.parent is InstanceCreationExpression) {
return;
}
if (node.typeArguments != null) return;
var type = node.type;
if (type == null) return;
var element = node.element;
if (element == null) return;
List<DartType> typeArguments;
var alias = type.alias;
if (alias != null) {
typeArguments = alias.typeArguments;
} else if (type is InterfaceType) {
typeArguments = type.typeArguments;
} else {
return;
}
if (!typeArguments.any((t) => t is DynamicType)) return;
if (element.metadata.hasOptionalTypeArgs) return;
if (node.parentEscapingTypeArguments
case AsExpression() ||
CastPattern() ||
IsExpression() ||
ObjectPattern() ||
TypeLiteral()) {
// Do not report a "strict raw type" warning in this case; too noisy,
// especially in the case of unstructured data parsing, like JSON and
// YAML.
return;
}
rule.reportAtNode(node, arguments: [type]);
}
}
extension on NamedType {
AstNode get parentEscapingTypeArguments {
var ancestor = parent!;
while (ancestor is TypeArgumentList || ancestor is NamedType) {
if (ancestor.parent case var grandancestor?) {
ancestor = grandancestor;
} else {
return ancestor;
}
}
return ancestor;
}
}
+51
View File
@@ -7878,6 +7878,57 @@ LinterLintCode:
}
}
```
noRawTypes:
type: lint
parameters:
Type type: the name of the generic type
problemMessage: "The generic type '#type' should have explicit type arguments but doesn't."
correctionMessage: "Use explicit type arguments for '#type'."
state:
stable: "3.13"
categories: [errorProne]
hasPublishedDocs: false
documentation: |-
#### Description
The analyzer produces this diagnostic when a generic type is used without
explicit type arguments, meaning it is a raw type.
#### Example
The following code produces this diagnostic because the generic class `C`
is used without a type argument:
```dart
class C<T> {}
[!C!] c = C<int>();
```
#### Common fixes
Provide explicit type arguments:
```dart
class C<T> {}
C<int> c = C<int>();
```
deprecatedDetails: |-
**DON'T** use raw types.
A raw type is a type annotation for a generic type which omits type
arguments. Developers may mistakenly believe that the type arguments are
inferred, but in fact each type parameter's bound is used, which can lead
to lost type information.
**BAD:**
```dart
List list = [1, 2, 3];
```
**GOOD:**
```dart
List<int> list = [1, 2, 3];
```
noRuntimetypeTostring:
type: lint
parameters: none
+2
View File
@@ -157,6 +157,7 @@ import 'no_leading_underscores_for_local_identifiers_test.dart'
as no_leading_underscores_for_local_identifiers;
import 'no_literal_bool_comparisons_test.dart' as no_literal_bool_comparisons;
import 'no_logic_in_create_state_test.dart' as no_logic_in_create_state;
import 'no_raw_types_test.dart' as no_raw_types;
import 'no_runtimeType_toString_test.dart' as no_runtimeType_toString;
import 'no_self_assignments_test.dart' as no_self_assignments;
import 'no_wildcard_variable_uses_test.dart' as no_wildcard_variable_uses;
@@ -454,6 +455,7 @@ void main() {
no_leading_underscores_for_local_identifiers.main();
no_literal_bool_comparisons.main();
no_logic_in_create_state.main();
no_raw_types.main();
no_runtimeType_toString.main();
no_self_assignments.main();
no_wildcard_variable_uses.main();
@@ -0,0 +1,483 @@
// 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(NoRawTypesTest);
});
}
@reflectiveTest
class NoRawTypesTest extends LintRuleTest {
@override
bool get addMetaPackageDep => true;
@override
String get lintRule => LintNames.no_raw_types;
test_asExpression() async {
await assertNoDiagnostics(r'''
void f(dynamic x) {
print(x as List);
}
''');
}
test_asExpression_typeArgument() async {
await assertNoDiagnostics(r'''
void f(dynamic x) {
print(x as List<List>);
}
''');
}
test_castPattern() async {
await assertNoDiagnostics(r'''
void f((Object, ) l) {
var (_ as List, ) = l;
}
''');
}
test_castPattern_typeArgument() async {
await assertNoDiagnostics(r'''
void f((Object, ) l) {
var (_ as List<List>, ) = l;
}
''');
}
test_constantPattern() async {
await assertNoDiagnostics(r'''
void f(C<int> c) {
switch (c) {
case const C():
}
}
class C<T> {
const C();
}
''');
}
test_functionParts_optionalTypeArg() async {
await assertNoDiagnostics(r'''
import 'package:meta/meta.dart';
@optionalTypeArgs
class C<T> {}
C f(int a) => C();
void g(C a) {}
''');
}
test_genericTypeArgument_extensionType_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
extension type E<T>(int i) {}
void f() {
<List<[!E!]>>[];
}
''');
}
test_genericTypeArgument_extensionType_withTypeArg() async {
await assertNoDiagnostics(r'''
extension type E<T>(int i) {}
void f() {
<List<E<int>>>[];
}
''');
}
test_genericTypeArgument_extensionTypeImplements_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
extension type E(List<int> i) implements [!Iterable!] {}
''');
}
test_genericTypeArgument_extensionTypeImplementsExtensionType_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
extension type E<T>(Iterable<T> i) {}
extension type F(List<int> j) implements [!E!] {}
''');
}
test_genericTypeArgument_extensionTypeRepresentationType_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
extension type E([!List!] i) {}
''');
}
test_genericTypeArgument_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void f() {
var a = <[!List!]>[];
}
''');
}
test_genericTypeArgument_withTypeArg() async {
await assertNoDiagnostics(r'''
void f() {
var a = <List<int>>[];
}
''');
}
test_instanceCreation() async {
await assertNoDiagnostics(r'''
var c = List.empty();
''');
}
test_isExpression() async {
await assertNoDiagnostics(r'''
void f(dynamic x) {
print(x is List);
print(x is List<dynamic>);
print(x is List<List>);
}
''');
}
test_localVariable_extensionType_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
extension type E<T>(int i) {}
void f() {
[!E!] e = E(1);
}
''');
}
test_localVariable_extensionType_withTypeArg() async {
await assertNoDiagnostics(r'''
extension type E<T>(int i) {}
void f() {
E<int> e = E<int>(1);
}
''');
}
test_localVariable_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void f() {
[!List!] a = [1, 2, 3];
}
''');
}
test_localVariable_withTypeArg() async {
await assertNoDiagnostics(r'''
void f() {
List<Object> a = [1, 2, 3];
print(a);
}
''');
}
test_mixinApplication_missing() async {
await assertDiagnosticsFromMarkdown(r'''
mixin class C<T> {}
class D = Object with [!C!];
''');
}
test_mixinApplication_withTypeArg() async {
await assertNoDiagnostics(r'''
mixin class C<T> {}
class D = Object with C<int>;
''');
}
test_nonFunctionTypeAlias_explicitTypeArg() async {
await assertNoDiagnostics('''
typedef List2<T> = List<T>;
void f(List2<int> a) {}
''');
}
test_nonFunctionTypeAlias_missingTypeArg() async {
await assertDiagnosticsFromMarkdown('''
typedef List2<T> = List<T>;
void f([!List2!] a) {}
''');
}
test_nonFunctionTypeAlias_optionalTypeArgs() async {
await assertNoDiagnostics('''
import 'package:meta/meta.dart';
@optionalTypeArgs
typedef List2<T> = List<T>;
void f(List2 a) {}
''');
}
test_objectPattern() async {
await assertNoDiagnostics(r'''
void f(Object o) {
switch (o) {
case List():
}
}
''');
}
test_parameter_default_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void f({[!List!] a = const []}) {}
''');
}
test_parameter_fieldFormal_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
class C {
Object a;
C([!List!] this.a);
}
''');
}
test_parameter_functionTyped_parameter_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void f(void a([!List!] p)) {}
''');
}
test_parameter_functionTyped_returnType_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void f([!List!] a()) {}
''');
}
test_parameter_primaryDeclaring_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
class C(final [!List!] a);
''');
}
test_parameter_simple_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void f([!List!] a) {}
''');
}
test_parameter_super_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
abstract class C {
Object a;
C(this.a);
}
class D extends C {
D([!List!] super.a);
}
''');
}
test_returnType_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
[!List!] f(int a) => [1, 2, 3];
''');
}
test_superclassWith_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
mixin class C<T> {}
class D extends Object with [!C!] {}
''');
}
test_superclassWith_withTypeArg() async {
await assertNoDiagnostics(r'''
mixin class C<T> {}
class D extends Object with C<int> {}
''');
}
test_topLevelField_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
[!List!] a = [];
''');
}
test_topLevelField_optionalTypeArg() async {
await assertNoDiagnostics(r'''
import 'package:meta/meta.dart';
@optionalTypeArgs
class C<T> {}
C a = C();
C get g => C();
void set s(C a) {}
''');
}
test_topLevelField_withTypeArg() async {
await assertNoDiagnostics(r'''
List<int> a = [];
List<num> get g => [];
void set s(List<double> a) {}
''');
}
test_topLevelGetter_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
[!List!] get g => [];
''');
}
test_topLevelSetter_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
void set s([!List!] a) {}
''');
}
test_typeAlias_classic_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
typedef T F1<T>(T _);
[!F1!] func = (a) => a;
''');
}
test_typeAlias_modern_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
typedef F1<T> = T Function(T);
[!F1!] func = (a) => a;
''');
}
test_typeAlias_modern_optionalTypeArgs() async {
await assertNoDiagnostics(r'''
import 'package:meta/meta.dart';
@optionalTypeArgs
typedef T F1<T>(T _);
@optionalTypeArgs
typedef F2<T> = T Function(T);
F1 f1 = (a) => a;
F2 f2 = (a) => a;
''');
}
test_typeAlias_modern_withTypeArg() async {
await assertNoDiagnostics(r'''
typedef T F1<T>(T _);
typedef F2<T> = T Function(T);
typedef F3 = T Function<T>(T);
F1<int> f1 = (a) => a;
F2<int> f2 = (a) => a;
F3 f3 = <T>(T a) => a;
''');
}
test_typeInClassDeclaration_optionalTypeArgs() async {
await assertNoDiagnostics(r'''
import 'package:meta/meta.dart';
@optionalTypeArgs
mixin class C<T> {}
class D extends C {}
class E extends Object with C {}
class F = Object with C;
class G implements C {}
''');
}
test_typeInConstructorName() async {
await assertNoDiagnostics(r'''
class C {
C();
C.named();
}
var c = C();
var d = C.named();
''');
}
test_typeInExtendedType_anonymous_missing() async {
await assertDiagnosticsFromMarkdown(r'''
extension on [!List!] {}
''');
}
test_typeInExtendedType_missing() async {
await assertDiagnosticsFromMarkdown(r'''
extension E on [!List!] {}
''');
}
test_typeInExtendedType_optionalTypeArgs() async {
await assertNoDiagnostics(r'''
import 'package:meta/meta.dart';
@optionalTypeArgs
class C<T> {}
extension E on C {}
extension on C {}
''');
}
test_typeInExtendedType_present() async {
await assertNoDiagnostics(r'''
extension E<T> on List<T> {}
extension F on List<int> {}
''');
}
test_typeInInterface_missing() async {
await assertDiagnosticsFromMarkdown(r'''
class C<T> {}
class D implements [!C!] {}
''');
}
test_typeInInterface_withTypeArg() async {
await assertNoDiagnostics(r'''
class C<T> {}
class D implements C<int> {}
''');
}
test_typeInSuperclass_missing() async {
await assertDiagnosticsFromMarkdown(r'''
class C<T> {}
class D extends [!C!] {}
''');
}
test_typeInSuperclass_withTypeArg() async {
await assertNoDiagnostics(r'''
class C<T> {}
class D extends C<int> {}
''');
}
test_typeLiteral_raw() async {
await assertNoDiagnostics(r'''
void f() {
var t = List;
print(t);
}
''');
}
test_typeParameterBound_missingTypeArg() async {
await assertDiagnosticsFromMarkdown(r'''
class C<T> {}
class D<T extends [!C!]> {}
''');
}
test_typeParameterBound_withTypeArg() async {
await assertNoDiagnostics(r'''
class C<T> {}
class D<S, T extends C<S>> {}
''');
}
}