diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index dd2fbe97a663..741ce0eca650 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -2198,6 +2198,8 @@ LintCode.omit_local_variable_types: status: hasFix LintCode.omit_obvious_local_variable_types: status: hasFix +LintCode.omit_obvious_property_types: + status: needsEvaluation LintCode.one_member_abstracts: status: noFix notes: |- @@ -2359,6 +2361,8 @@ LintCode.sort_unnamed_constructors_first: status: hasFix LintCode.specify_nonobvious_local_variable_types: status: hasFix +LintCode.specify_nonobvious_property_types: + status: needsEvaluation LintCode.test_types_in_equals: status: noFix LintCode.throw_in_finally: diff --git a/pkg/linter/CHANGELOG.md b/pkg/linter/CHANGELOG.md index 4ebdf1b288d3..43d99419ef4a 100644 --- a/pkg/linter/CHANGELOG.md +++ b/pkg/linter/CHANGELOG.md @@ -1,6 +1,8 @@ # 3.7.0-wip - deprecated lint: `package_api_docs` +- new _(experimental)_ lint: `omit_obvious_property_types` +- new _(experimental)_ lint: `specify_nonobvious_property_types` # 3.6.0 diff --git a/pkg/linter/example/all.yaml b/pkg/linter/example/all.yaml index edc355b3a048..f953a1cbd4fd 100644 --- a/pkg/linter/example/all.yaml +++ b/pkg/linter/example/all.yaml @@ -111,6 +111,7 @@ linter: - null_closures - omit_local_variable_types - omit_obvious_local_variable_types + - omit_obvious_property_types - one_member_abstracts - only_throw_errors - overridden_fields @@ -169,6 +170,7 @@ linter: - sort_pub_dependencies - sort_unnamed_constructors_first - specify_nonobvious_local_variable_types + - specify_nonobvious_property_types - test_types_in_equals - throw_in_finally - tighten_type_of_initializing_formals diff --git a/pkg/linter/lib/src/lint_codes.g.dart b/pkg/linter/lib/src/lint_codes.g.dart index ab85246a9754..130081a9b717 100644 --- a/pkg/linter/lib/src/lint_codes.g.dart +++ b/pkg/linter/lib/src/lint_codes.g.dart @@ -986,6 +986,12 @@ class LinterLintCode extends LintCode { correctionMessage: "Try removing the type annotation.", ); + static const LintCode omit_obvious_property_types = LinterLintCode( + LintNames.omit_obvious_property_types, + "The type annotation isn't needed because it is obvious.", + correctionMessage: "Try removing the type annotation.", + ); + static const LintCode one_member_abstracts = LinterLintCode( LintNames.one_member_abstracts, "Unnecessary use of an abstract class.", @@ -1481,6 +1487,12 @@ class LinterLintCode extends LintCode { correctionMessage: "Try adding a type annotation.", ); + static const LintCode specify_nonobvious_property_types = LinterLintCode( + LintNames.specify_nonobvious_property_types, + "A type annotation is needed because it isn't obvious.", + correctionMessage: "Try adding a type annotation.", + ); + static const LintCode test_types_in_equals = LinterLintCode( LintNames.test_types_in_equals, "Missing type test for '{0}' in '=='.", diff --git a/pkg/linter/lib/src/lint_names.g.dart b/pkg/linter/lib/src/lint_names.g.dart index 8732377c9e52..c5b115472e32 100644 --- a/pkg/linter/lib/src/lint_names.g.dart +++ b/pkg/linter/lib/src/lint_names.g.dart @@ -307,6 +307,9 @@ abstract final class LintNames { static const String omit_obvious_local_variable_types = 'omit_obvious_local_variable_types'; + static const String omit_obvious_property_types = + 'omit_obvious_property_types'; + static const String one_member_abstracts = 'one_member_abstracts'; static const String only_throw_errors = 'only_throw_errors'; @@ -451,6 +454,9 @@ abstract final class LintNames { static const String specify_nonobvious_local_variable_types = 'specify_nonobvious_local_variable_types'; + static const String specify_nonobvious_property_types = + 'specify_nonobvious_property_types'; + static const String super_goes_last = 'super_goes_last'; static const String test_types_in_equals = 'test_types_in_equals'; diff --git a/pkg/linter/lib/src/rules.dart b/pkg/linter/lib/src/rules.dart index 0ad4004e7a2f..8fcc7ae0a0b9 100644 --- a/pkg/linter/lib/src/rules.dart +++ b/pkg/linter/lib/src/rules.dart @@ -122,6 +122,7 @@ import 'rules/null_check_on_nullable_type_parameter.dart'; import 'rules/null_closures.dart'; import 'rules/omit_local_variable_types.dart'; import 'rules/omit_obvious_local_variable_types.dart'; +import 'rules/omit_obvious_property_types.dart'; import 'rules/one_member_abstracts.dart'; import 'rules/only_throw_errors.dart'; import 'rules/overridden_fields.dart'; @@ -184,6 +185,7 @@ import 'rules/sort_child_properties_last.dart'; import 'rules/sort_constructors_first.dart'; import 'rules/sort_unnamed_constructors_first.dart'; import 'rules/specify_nonobvious_local_variable_types.dart'; +import 'rules/specify_nonobvious_property_types.dart'; import 'rules/super_goes_last.dart'; import 'rules/test_types_in_equals.dart'; import 'rules/throw_in_finally.dart'; @@ -366,6 +368,7 @@ void registerLintRules() { ..register(NullClosures()) ..register(OmitLocalVariableTypes()) ..register(OmitObviousLocalVariableTypes()) + ..register(OmitObviousPropertyTypes()) ..register(OneMemberAbstracts()) ..register(OnlyThrowErrors()) ..register(OverriddenFields()) @@ -428,6 +431,7 @@ void registerLintRules() { ..register(SortUnnamedConstructorsFirst()) ..register(SuperGoesLast()) ..register(SpecifyNonObviousLocalVariableTypes()) + ..register(SpecifyNonObviousPropertyTypes()) ..register(TestTypesInEquals()) ..register(ThrowInFinally()) ..register(TightenTypeOfInitializingFormals()) diff --git a/pkg/linter/lib/src/rules/always_specify_types.dart b/pkg/linter/lib/src/rules/always_specify_types.dart index 715477349b94..63846ddb435f 100644 --- a/pkg/linter/lib/src/rules/always_specify_types.dart +++ b/pkg/linter/lib/src/rules/always_specify_types.dart @@ -26,6 +26,7 @@ class AlwaysSpecifyTypes extends LintRule { LintNames.avoid_types_on_closure_parameters, LintNames.omit_local_variable_types, LintNames.omit_obvious_local_variable_types, + LintNames.omit_obvious_property_types, ]; @override diff --git a/pkg/linter/lib/src/rules/omit_local_variable_types.dart b/pkg/linter/lib/src/rules/omit_local_variable_types.dart index 0f87904ab366..c64f52f6025d 100644 --- a/pkg/linter/lib/src/rules/omit_local_variable_types.dart +++ b/pkg/linter/lib/src/rules/omit_local_variable_types.dart @@ -23,6 +23,7 @@ class OmitLocalVariableTypes extends LintRule { List get incompatibleRules => const [ LintNames.always_specify_types, LintNames.specify_nonobvious_local_variable_types, + LintNames.specify_nonobvious_property_types, ]; @override diff --git a/pkg/linter/lib/src/rules/omit_obvious_property_types.dart b/pkg/linter/lib/src/rules/omit_obvious_property_types.dart new file mode 100644 index 000000000000..c4aef5da8af7 --- /dev/null +++ b/pkg/linter/lib/src/rules/omit_obvious_property_types.dart @@ -0,0 +1,66 @@ +// Copyright (c) 2024, 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/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +import '../analyzer.dart'; +import '../util/obvious_types.dart'; + +const _desc = + r'Omit obvious type annotations for top-level and static variables.'; + +class OmitObviousPropertyTypes extends LintRule { + OmitObviousPropertyTypes() + : super( + name: 'omit_obvious_property_types', + description: _desc, + state: State.experimental(), + ); + + @override + List get incompatibleRules => const ['always_specify_types']; + + @override + LintCode get lintCode => LinterLintCode.omit_obvious_property_types; + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this); + registry.addFieldDeclaration(this, visitor); + registry.addTopLevelVariableDeclaration(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitFieldDeclaration(FieldDeclaration node) => + _visitVariableDeclarationList(node.fields); + + @override + void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) => + _visitVariableDeclarationList(node.variables); + + void _visitVariableDeclarationList(VariableDeclarationList node) { + var staticType = node.type?.type; + if (staticType == null || staticType.isDartCoreNull) { + return; + } + for (var child in node.variables) { + var initializer = child.initializer; + if (initializer != null && !initializer.hasObviousType) { + return; + } + if (initializer?.staticType != staticType) { + return; + } + } + rule.reportLint(node.type); + } +} diff --git a/pkg/linter/lib/src/rules/specify_nonobvious_local_variable_types.dart b/pkg/linter/lib/src/rules/specify_nonobvious_local_variable_types.dart index 21cd74cf6854..266e3d30b12c 100644 --- a/pkg/linter/lib/src/rules/specify_nonobvious_local_variable_types.dart +++ b/pkg/linter/lib/src/rules/specify_nonobvious_local_variable_types.dart @@ -33,6 +33,7 @@ class SpecifyNonObviousLocalVariableTypes extends LintRule { var visitor = _Visitor(this); registry.addForStatement(this, visitor); registry.addPatternVariableDeclarationStatement(this, visitor); + registry.addSwitchExpression(this, visitor); registry.addSwitchStatement(this, visitor); registry.addVariableDeclarationStatement(this, visitor); } @@ -87,7 +88,10 @@ class _Visitor extends SimpleAstVisitor { } @override - void visitSwitchExpression(SwitchExpression node) {} + void visitSwitchExpression(SwitchExpression node) { + if (node.expression.hasObviousType) return; + node.cases.forEach(_PatternVisitor(rule).visitSwitchExpressionCase); + } @override void visitSwitchStatement(SwitchStatement node) { diff --git a/pkg/linter/lib/src/rules/specify_nonobvious_property_types.dart b/pkg/linter/lib/src/rules/specify_nonobvious_property_types.dart new file mode 100644 index 000000000000..432619b13238 --- /dev/null +++ b/pkg/linter/lib/src/rules/specify_nonobvious_property_types.dart @@ -0,0 +1,107 @@ +// Copyright (c) 2024, 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/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/element.dart'; + +import '../analyzer.dart'; +import '../util/obvious_types.dart'; + +const _desc = r'Specify non-obvious type annotations for local variables.'; + +class SpecifyNonObviousPropertyTypes extends LintRule { + SpecifyNonObviousPropertyTypes() + : super( + name: 'specify_nonobvious_property_types', + description: _desc, + state: State.experimental(), + ); + + @override + List get incompatibleRules => const ['omit_local_variable_types']; + + @override + LintCode get lintCode => LinterLintCode.specify_nonobvious_property_types; + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this); + registry.addFieldDeclaration(this, visitor); + registry.addTopLevelVariableDeclaration(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitFieldDeclaration(FieldDeclaration node) => + _visitVariableDeclarationList(node.fields, + isInstanceVariable: !node.isStatic); + + @override + void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) => + _visitVariableDeclarationList(node.variables, isInstanceVariable: false); + + void _visitVariableDeclarationList(VariableDeclarationList node, + {required bool isInstanceVariable}) { + var staticType = node.type?.type; + if (staticType != null && !staticType.isDartCoreNull) { + return; + } + bool aDeclaredTypeIsNeeded = false; + var variablesThatNeedAType = []; + for (var child in node.variables) { + var initializer = child.initializer; + if (isInstanceVariable) { + // Ignore this variable if the type comes from override inference. + bool ignoreThisVariable = false; + AstNode? owningDeclaration = node; + while (owningDeclaration != null) { + InterfaceElement? owningElement = switch (owningDeclaration) { + ClassDeclaration(:var declaredElement) => declaredElement, + MixinDeclaration(:var declaredElement) => declaredElement, + EnumDeclaration(:var declaredElement) => declaredElement, + ExtensionTypeDeclaration(:var declaredElement) => declaredElement, + _ => null, + }; + if (owningElement != null) { + var variableName = child.name.lexeme; + for (var superInterface in owningElement.allSupertypes) { + if (superInterface.getGetter(variableName) != null) { + ignoreThisVariable = true; + } + if (superInterface.getSetter(variableName) != null) { + ignoreThisVariable = true; + } + } + } + owningDeclaration = owningDeclaration.parent; + } + if (ignoreThisVariable) continue; + } + if (initializer == null) { + aDeclaredTypeIsNeeded = true; + variablesThatNeedAType.add(child); + } else { + if (!initializer.hasObviousType) { + aDeclaredTypeIsNeeded = true; + variablesThatNeedAType.add(child); + } + } + } + if (aDeclaredTypeIsNeeded) { + if (node.variables.length == 1) { + rule.reportLint(node); + } else { + // Multiple variables, report each of them separately. No fix. + variablesThatNeedAType.forEach(rule.reportLint); + } + } + } +} diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml index 002d943cf3f1..cd512e96c317 100644 --- a/pkg/linter/messages.yaml +++ b/pkg/linter/messages.yaml @@ -6965,6 +6965,56 @@ LintCode: **This rule is experimental.** It is being evaluated, and it may be changed or removed. Feedback on its behavior is welcome! The main issue is here: https://github.com/dart-lang/linter/issues/3480. + omit_obvious_property_types: + problemMessage: "The type annotation isn't needed because it is obvious." + correctionMessage: "Try removing the type annotation." + addedIn: "3.6-wip" + categories: [style] + hasPublishedDocs: false + deprecatedDetails: |- + Don't type annotate initialized top-level or static variables when the type is + obvious. + + **BAD:** + ```dart + final int myTopLevelVariable = 7; + + class A { + static String myStaticVariable = 'Hello'; + } + ``` + + **GOOD:** + ```dart + final myTopLevelVariable = 7; + + class A { + static myStaticVariable = 'Hello'; + } + ``` + + Sometimes the inferred type is not the type you want the variable to have. For + example, you may intend to assign values of other types later. You may also + wish to write a type annotation explicitly because the type of the initializing + expression is non-obvious and it will be helpful for future readers of the + code to document this type. Or you may wish to commit to a specific type such + that future updates of dependencies (in nearby code, in imports, anywhere) + will not silently change the type of that variable, thus introducing + compile-time errors or run-time bugs in locations where this variable is used. + In those cases, go ahead and annotate the variable with the type you want. + + **GOOD:** + ```dart + final num myTopLevelVariable = 7; + + class A { + static String? myStaticVariable = 'Hello'; + } + ``` + + **This rule is experimental.** It is being evaluated, and it may be changed + or removed. Feedback on its behavior is welcome! The main issue is here: + https://github.com/dart-lang/linter/issues/5101. one_member_abstracts: problemMessage: "Unnecessary use of an abstract class." correctionMessage: "Try making '{0}' a top-level function and removing the class." @@ -10394,6 +10444,7 @@ LintCode: correctionMessage: "Try adding a type annotation." addedIn: "3.6-wip" categories: [style] + hasPublishedDocs: false deprecatedDetails: |- Do type annotate initialized local variables when the type is non-obvious. @@ -10469,6 +10520,75 @@ LintCode: **This rule is experimental.** It is being evaluated, and it may be changed or removed. Feedback on its behavior is welcome! The main issue is here: https://github.com/dart-lang/linter/issues/3480. + specify_nonobvious_property_types: + problemMessage: "A type annotation is needed because it isn't obvious." + correctionMessage: "Try adding a type annotation." + addedIn: "3.6-wip" + categories: [style] + hasPublishedDocs: false + deprecatedDetails: |- + Do type annotate initialized top-level or static variables when the type is + non-obvious. + + Type annotations on top-level or static variables can serve as a request for + type inference, documenting the expected outcome of the type inference step, + and declaratively allowing the compiler and analyzer to solve the possibly + complex task of finding type arguments and annotations in the initializing + expression that yield the desired result. + + Type annotations on top-level or static variables can also inform readers about + the type of the initializing expression, which will allow them to proceed + reading the locations in code where this variable is used with known good + information about the type of the given variable (which may not be immediately + evident by looking at the initializing expression). + + An expression is considered to have a non-obvious type when it does not + have an obvious type. + + An expression e has an obvious type in the following cases: + + - e is a non-collection literal. For instance, 1, true, 'Hello, $name!'. + - e is a collection literal with actual type arguments. For instance, + {}. + - e is a list literal or a set literal where at least one element has an + obvious type, and all elements have the same type. For instance, [1, 2] and + { [true, false], [] }, but not [1, 1.5]. + - e is a map literal where all key-value pair have a key with an obvious type + and a value with an obvious type, and all keys have the same type, and all + values have the same type. For instance, { #a: [] }, but not + {1: 1, 2: true}. + - e is an instance creation expression whose class part is not raw. For + instance C(14) if C is a non-generic class, or C(14) if C accepts one + type argument, but not C(14) if C accepts one or more type arguments. + - e is a cascade whose target has an obvious type. For instance, + 1..isEven..isEven has an obvious type because 1 has an obvious type. + - e is a type cast. For instance, myComplexpression as int. + + **BAD:** + ```dart + final myTopLevelVariable = + genericFunctionWrittenByOtherFolks(with, args); + + class A { + static var myStaticVariable = + myTopLevelVariable.update('foo', null); + } + ``` + + **GOOD:** + ```dart + final Map myTopLevelVariable = + genericFunctionWrittenByOtherFolks(with, args); + + class A { + static Map myStaticVariable = + myTopLevelVariable.update('foo', null); + } + ``` + + **This rule is experimental.** It is being evaluated, and it may be changed + or removed. Feedback on its behavior is welcome! The main issue is here: + https://github.com/dart-lang/linter/issues/5101. super_goes_last: addedIn: "2.0" removedIn: "3.0" diff --git a/pkg/linter/test/rules/all.dart b/pkg/linter/test/rules/all.dart index 08357f01bfb9..729cb3b8974c 100644 --- a/pkg/linter/test/rules/all.dart +++ b/pkg/linter/test/rules/all.dart @@ -162,6 +162,7 @@ import 'null_closures_test.dart' as null_closures; import 'omit_local_variable_types_test.dart' as omit_local_variable_types; import 'omit_obvious_local_variable_types_test.dart' as omit_obvious_local_variable_types; +import 'omit_obvious_property_types_test.dart' as omit_obvious_property_types; import 'one_member_abstracts_test.dart' as one_member_abstracts; import 'only_throw_errors_test.dart' as only_throw_errors; import 'overridden_fields_test.dart' as overridden_fields; @@ -237,6 +238,8 @@ import 'sort_unnamed_constructors_first_test.dart' as sort_unnamed_constructors_first; import 'specify_nonobvious_local_variable_types_test.dart' as specify_nonobvious_local_variable_types; +import 'specify_nonobvious_property_types_test.dart' + as specify_nonobvious_property_types; 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' @@ -430,6 +433,7 @@ void main() { null_closures.main(); omit_local_variable_types.main(); omit_obvious_local_variable_types.main(); + omit_obvious_property_types.main(); one_member_abstracts.main(); only_throw_errors.main(); overridden_fields.main(); @@ -488,6 +492,7 @@ void main() { sort_pub_dependencies.main(); sort_unnamed_constructors_first.main(); specify_nonobvious_local_variable_types.main(); + specify_nonobvious_property_types.main(); test_types_in_equals.main(); throw_in_finally.main(); tighten_type_of_initializing_formals.main(); diff --git a/pkg/linter/test/rules/omit_obvious_property_types_test.dart b/pkg/linter/test/rules/omit_obvious_property_types_test.dart new file mode 100644 index 000000000000..bd0674cab5ee --- /dev/null +++ b/pkg/linter/test/rules/omit_obvious_property_types_test.dart @@ -0,0 +1,484 @@ +// Copyright (c) 2024, 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'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(OmitObviousPropertyTypesTest); + }); +} + +@reflectiveTest +class OmitObviousPropertyTypesTest extends LintRuleTest { + @override + String get lintRule => 'omit_obvious_property_types'; + + test_as_dynamic_static() async { + await assertDiagnostics(r''' +class A { + static dynamic i = n as dynamic; +} + +num n = 1; +''', [ + lint(19, 7), + ]); + } + + test_as_dynamic_topLevel() async { + await assertDiagnostics(r''' +dynamic i = n as dynamic; +num n = 1; +''', [ + lint(0, 7), + ]); + } + + test_as_static() async { + await assertDiagnostics(r''' +class A { + static int i = n as int; +} + +num n = 1; +''', [ + lint(19, 3), + ]); + } + + test_as_topLevel() async { + await assertDiagnostics(r''' +int i = n as int; +num n = 1; +''', [ + lint(0, 3), + ]); + } + + test_cascade_static() async { + await assertDiagnostics(r''' +class A { + static C c = C()..x..x..x; +} + +class C { + final x = 0; +} +''', [ + lint(19, 1), + ]); + } + + test_cascade_topLevel() async { + await assertDiagnostics(r''' +C c = C()..x..x..x; + +class C { + final x = 0; +} +''', [ + lint(0, 1), + ]); + } + + test_genericInvocation_paramIsType_static() async { + await assertNoDiagnostics(r''' +T bar(T d) => d; + +class A { + static String h = bar(''); +} +'''); + } + + test_genericInvocation_paramIsType_topLevel() async { + await assertNoDiagnostics(r''' +T bar(T d) => d; +String h = bar(''); +'''); + } + + test_genericInvocation_typeNeededForInference_static() async { + await assertNoDiagnostics(r''' +T bar(dynamic d) => d; + +class A { + static String h = bar(''); +} +'''); + } + + test_genericInvocation_typeNeededForInference_topLevel() async { + await assertNoDiagnostics(r''' +T bar(dynamic d) => d; +String h = bar(''); +'''); + } + + test_genericInvocation_typeParamProvided_static() async { + await assertNoDiagnostics(r''' +T bar(dynamic d) => d; + +class A { + static String h = bar(''); +} +'''); + } + + test_genericInvocation_typeParamProvided_topLevel() async { + await assertNoDiagnostics(r''' +T bar(dynamic d) => d; +String h = bar(''); +'''); + } + + test_instanceCreation_generic_ok_static() async { + await assertNoDiagnostics(r''' +class A { + static C c = C(); +} + +class C {} +'''); + } + + test_instanceCreation_generic_ok_topLevel() async { + await assertNoDiagnostics(r''' +C c = C(); + +class C {} +'''); + } + + test_instanceCreation_generic_static() async { + await assertDiagnostics(r''' +class A { + static C c = C(); +} + +class C {} +''', [ + lint(19, 6), + ]); + } + + test_instanceCreation_generic_topLevel() async { + await assertDiagnostics(r''' +C c = C(); + +class C {} +''', [ + lint(0, 6), + ]); + } + + test_instanceCreation_nonGeneric_static() async { + await assertDiagnostics(r''' +class A { + static C c = C(); +} + +class C {} +''', [ + lint(19, 1), + ]); + } + + test_instanceCreation_nonGeneric_topLevel() async { + await assertDiagnostics(r''' +C c = C(); + +class C {} +''', [ + lint(0, 1), + ]); + } + + test_list_ok1_static() async { + await assertNoDiagnostics(r''' +class A { + static List a = [1, true, 2]; +} +'''); + } + + test_list_ok1_topLevel() async { + await assertNoDiagnostics(r''' +List a = [1, true, 2]; +'''); + } + + test_list_ok2_static() async { + await assertNoDiagnostics(r''' +class A { + static List a = [1, foo(2), 3]; +} + +List foo(X x) => [x]; +'''); + } + + test_list_ok2_topLevel() async { + await assertNoDiagnostics(r''' +List a = [1, foo(2), 3]; + +List foo(X x) => [x]; +'''); + } + + test_list_static() async { + await assertDiagnostics(r''' +class A { + static List a = ['a', 'b', ('c' as dynamic) as String]; +} +''', [ + lint(19, 12), + ]); + } + + test_list_topLevel() async { + await assertDiagnostics(r''' +List a = ['a', 'b', ('c' as dynamic) as String]; +''', [ + lint(0, 12), + ]); + } + + test_literal_bool_static() async { + await assertDiagnostics(r''' +class A { + static bool b = true; +} +''', [ + lint(19, 4), + ]); + } + + test_literal_bool_topLevel() async { + await assertDiagnostics(r''' +bool b = true; +''', [ + lint(0, 4), + ]); + } + + test_literal_double_static() async { + await assertDiagnostics(r''' +class A { + static double d = 1.5; +} +''', [ + lint(19, 6), + ]); + } + + test_literal_double_topLevel() async { + await assertDiagnostics(r''' +double d = 1.5; +''', [ + lint(0, 6), + ]); + } + + // The type is not obvious. + test_literal_doubleTypedInt_static() async { + await assertNoDiagnostics(r''' +class A { + static double d = 1; +} +'''); + } + + // The type is not obvious. + test_literal_doubleTypedInt_topLevel() async { + await assertNoDiagnostics(r''' +double d = 1; +'''); + } + + test_literal_int_static() async { + await assertDiagnostics(r''' +class A { + static int i = 1; +} +''', [ + lint(19, 3), + ]); + } + + test_literal_int_topLevel() async { + await assertDiagnostics(r''' +int i = 1; +''', [ + lint(0, 3), + ]); + } + + // `Null` is not obvious, the inferred type is `dynamic`. + test_literal_null_static() async { + await assertNoDiagnostics(r''' +class A { + static Null nil = null; +} +'''); + } + + // `Null` is not obvious, the inferred type is `dynamic`. + test_literal_null_topLevel() async { + await assertNoDiagnostics(r''' +Null nil = null; +'''); + } + + test_literal_string_static() async { + await assertDiagnostics(r''' +class A { + static String s = "A string"; +} +''', [ + lint(19, 6), + ]); + } + + test_literal_string_topLevel() async { + await assertDiagnostics(r''' +String s = "A string"; +''', [ + lint(0, 6), + ]); + } + + test_literal_symbol_static() async { + await assertDiagnostics(r''' +class A { + static Symbol s = #print; +} +''', [ + lint(19, 6), + ]); + } + + test_literal_symbol_topLevel() async { + await assertDiagnostics(r''' +Symbol s = #print; +''', [ + lint(0, 6), + ]); + } + + test_local_multiple_ok_static() async { + await assertNoDiagnostics(r''' +class A { + static var a = 'a', b = 'b'; +} +'''); + } + + test_local_multiple_ok_topLevel() async { + await assertNoDiagnostics(r''' +var a = 'a', b = 'b'; +'''); + } + + test_map_ok1_static() async { + await assertNoDiagnostics(r''' +class A { + static Map a = {1: 'a', true: 'b'}; +} +'''); + } + + test_map_ok1_topLevel() async { + await assertNoDiagnostics(r''' +Map a = {1: 'a', true: 'b'}; +'''); + } + + test_map_ok2_static() async { + await assertNoDiagnostics(r''' +class A { + static Map a = {1: 'a', 2: #b}; +} +'''); + } + + test_map_ok2_topLevel() async { + await assertNoDiagnostics(r''' +Map a = {1: 'a', 2: #b}; +'''); + } + + test_map_ok3_static() async { + await assertNoDiagnostics(r''' +class A { + static Map a = {1: 'a', i: 'b'}; +} + +var i = 2; +'''); + } + + test_map_ok3_topLevel() async { + await assertNoDiagnostics(r''' +Map a = {1: 'a', i: 'b'}; +var i = 2; +'''); + } + + test_map_ok4_static() async { + await assertNoDiagnostics(r''' +class A { + static Map a = {1: 'a', 2: b}; +} + +var b = 'b'; +'''); + } + + test_map_ok4_topLevel() async { + await assertNoDiagnostics(r''' +Map a = {1: 'a', 2: b}; +var b = 'b'; +'''); + } + + test_map_static() async { + await assertDiagnostics(r''' +class A { + static Map a = {1.5: 'a'}; +} +''', [ + lint(19, 19), + ]); + } + + test_map_topLevel() async { + await assertDiagnostics(r''' +Map a = {1.5: 'a'}; +''', [ + lint(0, 19), + ]); + } + + test_multiple_static() async { + await assertDiagnostics(r''' +class A { + static String a = 'a', b = 'b'; +} +''', [ + lint(19, 6), + ]); + } + + test_multiple_topLevel() async { + await assertDiagnostics(r''' +String a = 'a', b = 'b'; +''', [ + lint(0, 6), + ]); + } +} diff --git a/pkg/linter/test/rules/specify_nonobvious_property_types_test.dart b/pkg/linter/test/rules/specify_nonobvious_property_types_test.dart new file mode 100644 index 000000000000..d81b582a83ce --- /dev/null +++ b/pkg/linter/test/rules/specify_nonobvious_property_types_test.dart @@ -0,0 +1,665 @@ +// Copyright (c) 2024, 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'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(SpecifyNonObviousPropertyTypesTest); + }); +} + +@reflectiveTest +class SpecifyNonObviousPropertyTypesTest extends LintRuleTest { + @override + String get lintRule => 'specify_nonobvious_property_types'; + + test_as_dynamic_instance() async { + await assertNoDiagnostics(r''' +class A { + var d = 1 as dynamic; +} +'''); + } + + test_as_dynamic_static() async { + await assertNoDiagnostics(r''' +class A { + static var d = 1 as dynamic; +} +'''); + } + + test_as_dynamic_topLevel() async { + await assertNoDiagnostics(r''' +var d = 1 as dynamic; +'''); + } + + test_as_instance() async { + await assertNoDiagnostics(r''' +class A { + var d = 1 as num; +} +'''); + } + + test_as_static() async { + await assertNoDiagnostics(r''' +class A { + static var d = 1 as num; +} +'''); + } + + test_as_topLevel() async { + await assertNoDiagnostics(r''' +var d = 1 as num; +'''); + } + + test_cascade_instance() async { + await assertNoDiagnostics(r''' +class A { + var c = C()..x..x..x; +} + +class C { + final int x = 0; +} +'''); + } + + test_cascade_static() async { + await assertNoDiagnostics(r''' +class A { + static var c = C()..x..x..x; +} + +class C { + final int x = 0; +} +'''); + } + + test_cascade_topLevel() async { + await assertNoDiagnostics(r''' +var a = A()..x..x..x; + +class A { + final int x = 0; +} +'''); + } + + test_genericInvocation_paramIsType_instance() async { + await assertDiagnostics(r''' +class A { + final h = bar(''); +} + +T bar(T d) => d; +''', [ + lint(12, 17), + ]); + } + + test_genericInvocation_paramIsType_ok_instance() async { + await assertNoDiagnostics(r''' +class A { + String h = bar(''); +} + +T bar(T d) => d; +'''); + } + + test_genericInvocation_paramIsType_ok_static() async { + await assertNoDiagnostics(r''' +class A { + static String h = bar(''); +} + +T bar(T d) => d; +'''); + } + + test_genericInvocation_paramIsType_ok_topLevel() async { + await assertNoDiagnostics(r''' +String h = bar(''); +T bar(T d) => d; +'''); + } + + test_genericInvocation_paramIsType_static() async { + await assertDiagnostics(r''' +class A { + static final h = bar(''); +} + +T bar(T d) => d; +''', [ + lint(19, 17), + ]); + } + + test_genericInvocation_paramIsType_topLevel() async { + await assertDiagnostics(r''' +final h = bar(''); + +T bar(T d) => d; +''', [ + lint(0, 17), + ]); + } + + test_genericInvocation_typeNeededForInference_instance() async { + await assertDiagnostics(r''' +class A { + static var h = bar(''); +} + +T bar(dynamic d) => d; +''', [ + lint(19, 15), + ]); + } + + test_genericInvocation_typeNeededForInference_ok_instance() async { + await assertNoDiagnostics(r''' +class A { + String h = bar(''); +} + +T bar(dynamic d) => d; +'''); + } + + test_genericInvocation_typeNeededForInference_ok_static() async { + await assertNoDiagnostics(r''' +class A { + static String h = bar(''); +} + +T bar(dynamic d) => d; +'''); + } + + test_genericInvocation_typeNeededForInference_ok_topLevel() async { + await assertNoDiagnostics(r''' +String h = bar(''); +T bar(dynamic d) => d; +'''); + } + + test_genericInvocation_typeNeededForInference_static() async { + await assertDiagnostics(r''' +class A { + static var h = bar(''); +} + +T bar(dynamic d) => d; +''', [ + lint(19, 15), + ]); + } + + test_genericInvocation_typeNeededForInference_topLevel() async { + await assertDiagnostics(r''' +var h = bar(''); +T bar(dynamic d) => d; +''', [ + lint(0, 15), + ]); + } + + test_genericInvocation_typeParamProvided_instance() async { + await assertDiagnostics(r''' +class A { + var h = bar(''); +} + +T bar(dynamic d) => d; +''', [ + lint(12, 23), + ]); + } + + test_genericInvocation_typeParamProvided_ok_instance() async { + await assertNoDiagnostics(r''' +class A { + String h = bar(''); +} + +T bar(dynamic d) => d; +'''); + } + + test_genericInvocation_typeParamProvided_ok_static() async { + await assertNoDiagnostics(r''' +class A { + static String h = bar(''); +} + +T bar(dynamic d) => d; +'''); + } + + test_genericInvocation_typeParamProvided_ok_topLevel() async { + await assertNoDiagnostics(r''' +String h = bar(''); +T bar(dynamic d) => d; +'''); + } + + test_genericInvocation_typeParamProvided_static() async { + await assertDiagnostics(r''' +class A { + static var h = bar(''); +} + +T bar(dynamic d) => d; +''', [ + lint(19, 23), + ]); + } + + test_genericInvocation_typeParamProvided_topLevel() async { + await assertDiagnostics(r''' +var h = bar(''); +T bar(dynamic d) => d; +''', [ + lint(0, 23), + ]); + } + + test_instanceCreation_generic_instance() async { + await assertDiagnostics(r''' +class A { + var c = C(1); +} + +class C { + C(X x); +} +''', [ + lint(12, 12), + ]); + } + + test_instanceCreation_generic_ok1_instance() async { + await assertNoDiagnostics(r''' +class A { + C c = C(); +} + +class C {} +'''); + } + + test_instanceCreation_generic_ok1_static() async { + await assertNoDiagnostics(r''' +class A { + static C c = C(); +} + +class C {} +'''); + } + + test_instanceCreation_generic_ok1_topLevel() async { + await assertNoDiagnostics(r''' +A a = A(); + +class A {} +'''); + } + + test_instanceCreation_generic_ok2_instance() async { + await assertNoDiagnostics(r''' +class A { + C a = C(); +} + +class C {} +'''); + } + + test_instanceCreation_generic_ok2_static() async { + await assertNoDiagnostics(r''' +class A { + static C a = C(); +} + +class C {} +'''); + } + + test_instanceCreation_generic_ok2_topLevel() async { + await assertNoDiagnostics(r''' +A a = A(); + +class A {} +'''); + } + + test_instanceCreation_generic_static() async { + await assertDiagnostics(r''' +class A { + static var c = C(1); +} + +class C { + C(X x); +} +''', [ + lint(19, 12), + ]); + } + + test_instanceCreation_generic_topLevel() async { + await assertDiagnostics(r''' +var a = A(1); + +class A { + A(X x); +} +''', [ + lint(0, 12), + ]); + } + + test_instanceCreation_nonGeneric_instance() async { + await assertNoDiagnostics(r''' +class A { + C c = C(); +} + +class C {} +'''); + } + + test_instanceCreation_nonGeneric_static() async { + await assertNoDiagnostics(r''' +class A { + static C c = C(); +} + +class C {} +'''); + } + + test_instanceCreation_nonGeneric_topLevel() async { + await assertNoDiagnostics(r''' +A a = A(); + +class A {} +'''); + } + + test_literal_bool_instance() async { + await assertNoDiagnostics(r''' +class A { + static bool b = true; +} +'''); + } + + test_literal_bool_static() async { + await assertNoDiagnostics(r''' +class A { + static bool b = true; +} +'''); + } + + test_literal_bool_topLevel() async { + await assertNoDiagnostics(r''' +bool b = true; +'''); + } + + test_literal_double_instance() async { + await assertNoDiagnostics(r''' +class A { + double d = 1.5; +} +'''); + } + + test_literal_double_static() async { + await assertNoDiagnostics(r''' +class A { + static double d = 1.5; +} +'''); + } + + test_literal_double_topLevel() async { + await assertNoDiagnostics(r''' +double d = 1.5; +'''); + } + + // The type is not obvious. + test_literal_doubleTypedInt_instance() async { + await assertNoDiagnostics(r''' +class A { + double d = 1; +} +'''); + } + + // The type is not obvious. + test_literal_doubleTypedInt_static() async { + await assertNoDiagnostics(r''' +class A { + static double d = 1; +} +'''); + } + + // The type is not obvious. + test_literal_doubleTypedInt_topLevel() async { + await assertNoDiagnostics(r''' +double d = 1; +'''); + } + + test_literal_int_instance() async { + await assertNoDiagnostics(r''' +class A { + int i = 1; +} +'''); + } + + test_literal_int_static() async { + await assertNoDiagnostics(r''' +class A { + static int i = 1; +} +'''); + } + + test_literal_int_topLevel() async { + await assertNoDiagnostics(r''' +int i = 1; +'''); + } + + // `Null` is not obvious, the inferred type is `dynamic`. + test_literal_null_instance() async { + await assertNoDiagnostics(r''' +class A { + Null nil = null; +} +'''); + } + + // `Null` is not obvious, the inferred type is `dynamic`. + test_literal_null_static() async { + await assertNoDiagnostics(r''' +class A { + static Null nil = null; +} +'''); + } + + // `Null` is not obvious, the inferred type is `dynamic`. + test_literal_null_topLevel() async { + await assertNoDiagnostics(r''' +Null nil = null; +'''); + } + + test_literal_string_instance() async { + await assertNoDiagnostics(r''' +class A { + String s = "A string"; +} +'''); + } + + test_literal_string_static() async { + await assertNoDiagnostics(r''' +class A { + static String s = "A string"; +} +'''); + } + + test_literal_string_topLevel() async { + await assertNoDiagnostics(r''' +String s = "A string"; +'''); + } + + test_literal_symbol_instance() async { + await assertNoDiagnostics(r''' +class A { + Symbol s = #print; +} +'''); + } + + test_literal_symbol_static() async { + await assertNoDiagnostics(r''' +class A { + static Symbol s = #print; +} +'''); + } + + test_literal_symbol_topLevel() async { + await assertNoDiagnostics(r''' +Symbol s = #print; +'''); + } + + test_multiple_instance() async { + await assertDiagnostics(r''' +class A { + var a = 'a' + 'a', b = 'b' * 2; +} +''', [ + lint(16, 13), + lint(31, 11), + ]); + } + + test_multiple_ok_instance() async { + await assertNoDiagnostics(r''' +class A { + String a = 'a' + 'a', b = 'b'.toString(); +} +'''); + } + + test_multiple_ok_static() async { + await assertNoDiagnostics(r''' +class A { + static String a = 'a' + 'a', b = 'b'.toString(); +} +'''); + } + + test_multiple_ok_topLevel() async { + await assertNoDiagnostics(r''' +String a = 'a' + 'a', b = 'b'.toString(); +'''); + } + + test_multiple_static() async { + await assertDiagnostics(r''' +class A { + static var a = 'a' + 'a', b = 'b' * 2; +} +''', [ + lint(23, 13), + lint(38, 11), + ]); + } + + test_multiple_topLevel() async { + await assertDiagnostics(r''' +var a = 'a' + 'a', b = 'b' * 2; +''', [ + lint(4, 13), + lint(19, 11), + ]); + } + + test_override_getter_initialized() async { + await assertNoDiagnostics(r''' +class B implements A { + var x = 'Hello, ' + 'world!'; +} + +abstract class A { + String get x; +} +'''); + } + + test_override_getter_uninitialized() async { + await assertNoDiagnostics(r''' +class B implements A { + var x; + B(this.x); +} + +abstract class A { + int get x; +} +'''); + } + + test_override_setter_initialized() async { + await assertNoDiagnostics(r''' +class B implements A { + var x = 'Hello, ' + 'world!'; +} + +abstract class A { + set x (String _); +} +'''); + } + + test_override_setter_uninitialized() async { + await assertNoDiagnostics(r''' +class B implements A { + var x; + B(this.x); +} + +abstract class A { + set x(int _); +} +'''); + } +} diff --git a/pkg/linter/tool/machine/rules.json b/pkg/linter/tool/machine/rules.json index 4bd2b89a3172..326b10fd9362 100644 --- a/pkg/linter/tool/machine/rules.json +++ b/pkg/linter/tool/machine/rules.json @@ -60,7 +60,8 @@ "incompatible": [ "avoid_types_on_closure_parameters", "omit_local_variable_types", - "omit_obvious_local_variable_types" + "omit_obvious_local_variable_types", + "omit_obvious_property_types" ], "sets": [], "fixStatus": "hasFix",