diff --git a/example/all.yaml b/example/all.yaml index 2b069d5c7..ed28b9e54 100644 --- a/example/all.yaml +++ b/example/all.yaml @@ -47,6 +47,7 @@ linter: - avoid_types_on_closure_parameters - avoid_unnecessary_containers - avoid_unused_constructor_parameters + - avoid_var_declarations - avoid_void_async - avoid_web_libraries_in_flutter - await_only_futures diff --git a/lib/src/rules.dart b/lib/src/rules.dart index d0c80b6a8..8a391f88c 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -49,6 +49,7 @@ import 'rules/avoid_types_as_parameter_names.dart'; import 'rules/avoid_types_on_closure_parameters.dart'; import 'rules/avoid_unnecessary_containers.dart'; import 'rules/avoid_unused_constructor_parameters.dart'; +import 'rules/avoid_var_declarations.dart'; import 'rules/avoid_void_async.dart'; import 'rules/avoid_web_libraries_in_flutter.dart'; import 'rules/await_only_futures.dart'; @@ -256,6 +257,7 @@ void registerLintRules({bool inTestMode = false}) { ..register(AvoidTypesOnClosureParameters()) ..register(AvoidUnnecessaryContainers()) ..register(AvoidUnusedConstructorParameters()) + ..register(AvoidVarDeclarations()) ..register(AvoidVoidAsync()) ..register(AvoidWebLibrariesInFlutter()) ..register(AwaitOnlyFutures()) diff --git a/lib/src/rules/avoid_var_declarations.dart b/lib/src/rules/avoid_var_declarations.dart new file mode 100644 index 000000000..ffe13d963 --- /dev/null +++ b/lib/src/rules/avoid_var_declarations.dart @@ -0,0 +1,147 @@ +// Copyright (c) 2022, 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'; + +const _desc = r'Avoid declaring variables with var.'; + +const _details = r''' + +**DO** declare a variable using its explicit type if it is reassigned, or final, +if it is not. + +Declaring a variable using its explicit type is slightly more verbose but +improves readability, adds self-documentation and makes sure that you are not +dependant on compiler-inferred types. Declaring variables as final is good +practice because it helps avoiding accidental reassignments and allows the +compiler to do optimization. + +**BAD:** +```dart +class Person { + var name = 'Bella'; + var age = 64; + var ageAtBirth = 0; + + void celebratesBirthday() => age++; + void addsSecondName(String secondName) => name = '$name $secondName'; +} +``` + +**GOOD:** +```dart +class Person { + String name = 'Bella'; + int age = 64; + static const ageAtBirth = 0; + + void celebratesBirthday() => age++; + void addsSecondName(String secondName) => name = '$name $secondName'; +} +``` + +**BAD:** +```dart +double foo() { + var x = 20; + x += 3; + var y = x / 3; + return y; +} +``` + +**GOOD:** +```dart +double foo() { + int x = 20; + x += 3; + final y = x / 3; + return y; +} +``` + +**BAD:** +```dart +for (var x in [1, 2, 3]) { + print(x); +} +``` + +**GOOD:** +```dart +for (int x in [1, 2, 3]) { + x = x + x; + print(x); +} +``` + +**GOOD:** +```dart +for (final x in [1, 2, 3]) { + print(x); +} +``` +'''; + +class AvoidVarDeclarations extends LintRule { + AvoidVarDeclarations() + : super( + name: 'avoid_var_declarations', + description: _desc, + details: _details, + group: Group.style); + + @override + List get incompatibleRules => + const ['unnecessary_final', 'omit_local_variable_types']; + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this); + registry + ..addVariableDeclarationList(this, visitor) + ..addDeclaredIdentifier(this, visitor) + ..addSimpleFormalParameter(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitVariableDeclarationList(VariableDeclarationList node) { + if (!node.isConst && + !node.isFinal && + node.keyword != null && + node.type == null) { + rule.reportLintForToken(node.keyword); + } + } + + @override + void visitDeclaredIdentifier(DeclaredIdentifier node) { + if (!node.isConst && + !node.isFinal && + node.keyword != null && + node.type == null) { + rule.reportLintForToken(node.keyword); + } + } + + @override + void visitSimpleFormalParameter(SimpleFormalParameter param) { + if (!param.isConst && + !param.isFinal && + param.keyword != null && + param.type == null) { + rule.reportLintForToken(param.keyword); + } + } +} diff --git a/lib/src/rules/omit_local_variable_types.dart b/lib/src/rules/omit_local_variable_types.dart index 650c742a1..990b217ae 100644 --- a/lib/src/rules/omit_local_variable_types.dart +++ b/lib/src/rules/omit_local_variable_types.dart @@ -73,7 +73,8 @@ class OmitLocalVariableTypes extends LintRule { group: Group.style); @override - List get incompatibleRules => const ['always_specify_types']; + List get incompatibleRules => + const ['always_specify_types', 'avoid_var_declarations']; @override void registerNodeProcessors( diff --git a/lib/src/rules/unnecessary_final.dart b/lib/src/rules/unnecessary_final.dart index a082d1ea4..84a82c10c 100644 --- a/lib/src/rules/unnecessary_final.dart +++ b/lib/src/rules/unnecessary_final.dart @@ -44,8 +44,11 @@ class UnnecessaryFinal extends LintRule { group: Group.style); @override - List get incompatibleRules => - const ['prefer_final_locals', 'prefer_final_parameters']; + List get incompatibleRules => const [ + 'prefer_final_locals', + 'prefer_final_parameters', + 'avoid_var_declarations' + ]; @override void registerNodeProcessors( diff --git a/test_data/rules/avoid_var_declarations.dart b/test_data/rules/avoid_var_declarations.dart new file mode 100644 index 000000000..edfe09e6b --- /dev/null +++ b/test_data/rules/avoid_var_declarations.dart @@ -0,0 +1,93 @@ +// Copyright (c) 2022, 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. + +// test w/ `dart test -N avoid_var_declarations` + +const a = 1; // OK +const int b = 1; // OK +final c = 1; // OK +final int d = 1; // OK +int e = 1; // OK +int? f = null; // OK +var g; // LINT +var h = 1; // LINT +var i = null; // LINT +var j = []; // LINT +var k = {}; // LINT +var _; // LINT +var l = int; // LINT +var m = () {}; // LINT +var n = Thing(5); // LINT +late var o; // LINT +late int p; // OK +late int? q; // OK + +f1(int i) {} // OK +f2(var i) {} // LINT +f3(int number, var i) {} // LINT +f4({var i}) {} // LINT +f5({required int i}) {} // OK +f6({required var i}) {} // LINT +f7([int? i]) {} // OK +f8([var i]) {} // LINT +f9() { + var i; // LINT +} +f10() { + int? i; // OK +} +f11() { + final i = 'a'; // OK +} + +void l1() { + for (var i in [1, 2, 3]) { // LINT + print(i); + } + + for (final i in [1, 2, 3]) { // OK + print(i); + } + + for (int i in [1, 2, 3]) { // OK + i += 1; + print(i); + } + + int j; + for (j in [1, 2, 3]) { // OK + print(j); + } +} + +void l2() { + for (var i = 0; i < 3; i++) { // LINT + print(i); + } + + for (int i = 0; i < 3; i++) { // OK + print(i); + } +} + +void listen(void Function(Object event) onData) {} // OK + +f12() { + listen((var _) {}); // LINT +} + +class Thing{ + Thing(var x) { // LINT + var u; // LINT + listen((var _) {}); // LINT + } + Thing.named({var x}); // LINT + + static var a; // LINT + static const b = 'b'; // OK + + var j; // LINT + late var k; // LINT + late int i; // OK +}