diff --git a/example/all.yaml b/example/all.yaml index 62a0c4388..4620745ee 100644 --- a/example/all.yaml +++ b/example/all.yaml @@ -6,6 +6,9 @@ linter: - always_put_control_body_on_new_line - always_put_required_named_parameters_first - always_require_non_null_named_parameters + - always_specify_type_annotations + - always_specify_type_arguments_for_classes + - always_specify_type_arguments_for_methods - always_specify_types - annotate_overrides - avoid_annotating_with_dynamic diff --git a/lib/src/rules.dart b/lib/src/rules.dart index 6a6bfbd1f..41280a538 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -7,6 +7,9 @@ import 'package:linter/src/rules/always_declare_return_types.dart'; import 'package:linter/src/rules/always_put_control_body_on_new_line.dart'; import 'package:linter/src/rules/always_put_required_named_parameters_first.dart'; import 'package:linter/src/rules/always_require_non_null_named_parameters.dart'; +import 'package:linter/src/rules/always_specify_type_annotations.dart'; +import 'package:linter/src/rules/always_specify_type_arguments_for_classes.dart'; +import 'package:linter/src/rules/always_specify_type_arguments_for_methods.dart'; import 'package:linter/src/rules/always_specify_types.dart'; import 'package:linter/src/rules/annotate_overrides.dart'; import 'package:linter/src/rules/avoid_annotating_with_dynamic.dart'; @@ -138,6 +141,9 @@ void registerLintRules() { ..register(new AlwaysPutControlBodyOnNewLine()) ..register(new AlwaysPutRequiredNamedParametersFirst()) ..register(new AlwaysRequireNonNullNamedParameters()) + ..register(new AlwaysSpecifyTypeAnnotations()) + ..register(new AlwaysSpecifyTypeArgumentsForClasses()) + ..register(new AlwaysSpecifyTypeArgumentsForMethods()) ..register(new AlwaysSpecifyTypes()) ..register(new AnnotateOverrides()) ..register(new AvoidAnnotatingWithDynamic()) diff --git a/lib/src/rules/always_specify_type_annotations.dart b/lib/src/rules/always_specify_type_annotations.dart new file mode 100644 index 000000000..211c0ff49 --- /dev/null +++ b/lib/src/rules/always_specify_type_annotations.dart @@ -0,0 +1,80 @@ +// Copyright (c) 2018, 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:linter/src/analyzer.dart'; +import 'package:linter/src/utils.dart'; + +const _desc = r'Specify type annotations.'; + +const _details = r''' + +**DO** specify type annotations. + +**GOOD:** +``` +int foo = 10; +final Bar bar = new Bar(); +String baz = 'hello'; +const int quux = 20; +``` + +**BAD:** +``` +var foo = 10; +final bar = new Bar(); +const quux = 20; +``` +'''; + +class AlwaysSpecifyTypeAnnotations extends LintRule implements NodeLintRule { + AlwaysSpecifyTypeAnnotations() + : super( + name: 'always_specify_type_annotations', + description: _desc, + details: _details, + group: Group.style); + + @override + void registerNodeProcessors(NodeLintRegistry registry) { + final visitor = new _Visitor(this); + registry.addDeclaredIdentifier(this, visitor); + registry.addSimpleFormalParameter(this, visitor); + registry.addVariableDeclarationList(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitDeclaredIdentifier(DeclaredIdentifier node) { + if (node.type == null) { + rule.reportLintForToken(node.keyword); + } + } + + @override + void visitSimpleFormalParameter(SimpleFormalParameter param) { + if (param.type == null && + param.identifier != null && + !isJustUnderscores(param.identifier.name)) { + if (param.keyword != null) { + rule.reportLintForToken(param.keyword); + } else { + rule.reportLint(param); + } + } + } + + @override + void visitVariableDeclarationList(VariableDeclarationList list) { + if (list.type == null) { + rule.reportLintForToken(list.keyword); + } + } +} diff --git a/lib/src/rules/always_specify_type_arguments_for_classes.dart b/lib/src/rules/always_specify_type_arguments_for_classes.dart new file mode 100644 index 000000000..66ffc4daa --- /dev/null +++ b/lib/src/rules/always_specify_type_arguments_for_classes.dart @@ -0,0 +1,131 @@ +// Copyright (c) 2018, 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 'package:analyzer/dart/element/type.dart'; +import 'package:linter/src/analyzer.dart'; + +const _desc = r'Specify type arguments for classes.'; + +const _details = r''' + +**DO** specify type arguments for classes. + +**GOOD:** +``` +final a = []; +final b = new List(); +final c = {}; +final d = new Map(); +``` + +**BAD:** +``` +final a = []; +final b = new List(); +final c = {}; +final d = new Map(); +``` + +NOTE: Using the the `@optionalTypeArgs` annotation in the `meta` package, API +authors can special-case type variables whose type needs to by dynamic but whose +declaration should be treated as optional. For example, suppose you have a +`Key` object whose type parameter you'd like to treat as optional. Using the +`@optionalTypeArgs` would look like this: + +``` +import 'package:meta/meta.dart'; + +@optionalTypeArgs +class Key { + ... +} + +main() { + Key s = new Key(); // OK! +} +``` + +'''; + +/// The name of `meta` library, used to define analysis annotations. +String _META_LIB_NAME = 'meta'; + +/// The name of the top-level variable used to mark a Class as having optional +/// type args. +String _OPTIONAL_TYPE_ARGS_VAR_NAME = 'optionalTypeArgs'; + +bool _isOptionallyParameterized(ParameterizedType type) { + List metadata = type.element?.metadata; + if (metadata != null) { + return metadata + .any((ElementAnnotation a) => _isOptionalTypeArgs(a.element)); + } + return false; +} + +bool _isOptionalTypeArgs(Element element) => + element is PropertyAccessorElement && + element.name == _OPTIONAL_TYPE_ARGS_VAR_NAME && + element.library?.name == _META_LIB_NAME; + +class AlwaysSpecifyTypeArgumentsForClasses extends LintRule + implements NodeLintRule { + AlwaysSpecifyTypeArgumentsForClasses() + : super( + name: 'always_specify_type_arguments_for_classes', + description: _desc, + details: _details, + group: Group.style); + + @override + void registerNodeProcessors(NodeLintRegistry registry) { + final visitor = new _Visitor(this); + registry.addListLiteral(this, visitor); + registry.addMapLiteral(this, visitor); + registry.addTypeName(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitListLiteral(ListLiteral literal) { + _checkLiteral(literal); + } + + @override + void visitMapLiteral(MapLiteral literal) { + _checkLiteral(literal); + } + + void _checkLiteral(TypedLiteral literal) { + if (literal.typeArguments == null) { + rule.reportLintForToken(literal.beginToken); + } + } + + // Future kernel API. + void visitNamedType(NamedType namedType) { + DartType type = namedType.type; + if (type is ParameterizedType) { + if (type.typeParameters.isNotEmpty && + namedType.typeArguments == null && + namedType.parent is! IsExpression && + !_isOptionallyParameterized(type)) { + rule.reportLint(namedType); + } + } + } + + @override + void visitTypeName(NamedType typeName) { + visitNamedType(typeName); + } +} diff --git a/lib/src/rules/always_specify_type_arguments_for_methods.dart b/lib/src/rules/always_specify_type_arguments_for_methods.dart new file mode 100644 index 000000000..79a1b9924 --- /dev/null +++ b/lib/src/rules/always_specify_type_arguments_for_methods.dart @@ -0,0 +1,109 @@ +// Copyright (c) 2015, 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 'package:analyzer/dart/element/type.dart'; +import 'package:linter/src/analyzer.dart'; + +const _desc = r'Specify type arguments for methods.'; + +const _details = r''' + +**DO** specify type arguments for methods. + +**GOOD:** +``` +void m(T t1, T t2) {} +m(1, 2); +``` + +**BAD:** +``` +void m(T t1, T t2) {} +m(1, 2); +``` + +NOTE: Using the the `@optionalTypeArgs` annotation in the `meta` package, API +authors can special-case type variables whose type needs to by dynamic but whose +declaration should be treated as optional. For example, suppose you have a +`m` function whose type parameter you'd like to treat as optional. Using the +`@optionalTypeArgs` would look like this: + +``` +import 'package:meta/meta.dart'; + +@optionalTypeArgs +void m(T t1, T t2) {} + +main() { + m(1, 2); // OK! +} +``` + +'''; + +/// The name of `meta` library, used to define analysis annotations. +String _META_LIB_NAME = 'meta'; + +/// The name of the top-level variable used to mark a Class as having optional +/// type args. +String _OPTIONAL_TYPE_ARGS_VAR_NAME = 'optionalTypeArgs'; + +bool _isOptionalTypeArgs(Element element) => + element is PropertyAccessorElement && + element.name == _OPTIONAL_TYPE_ARGS_VAR_NAME && + element.library?.name == _META_LIB_NAME; + +class AlwaysSpecifyTypeArgumentsForMethods extends LintRule + implements NodeLintRule { + AlwaysSpecifyTypeArgumentsForMethods() + : super( + name: 'always_specify_type_arguments_for_methods', + description: _desc, + details: _details, + group: Group.style); + + @override + void registerNodeProcessors(NodeLintRegistry registry) { + final visitor = new _Visitor(this); + registry.addMethodInvocation(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + @override + void visitMethodInvocation(MethodInvocation node) { + if (node.typeArguments == null) { + final element = node.methodName.staticElement; + if (element is FunctionTypedElement && + element.typeParameters.isNotEmpty && + !_isAllowed(element) && + !element.metadata.any((a) => _isOptionalTypeArgs(a.element))) { + rule.reportLint(node.methodName); + } + } + } + + bool _isAllowed(FunctionTypedElement element) { + final returnType = element.returnType; + + // Special case for methods with return type and parameter types identical + // and equal to the type parameter on function + // eg. T m(T p1, T p2); + if (returnType is TypeParameterType) { + if (returnType.element.enclosingElement == element && + element.parameters.every((p) => returnType == p.type)) { + return true; + } + } + + return false; + } +} diff --git a/test/rules/always_specify_type_annotations.dart b/test/rules/always_specify_type_annotations.dart new file mode 100644 index 000000000..4688f048d --- /dev/null +++ b/test/rules/always_specify_type_annotations.dart @@ -0,0 +1,55 @@ +// Copyright (c) 2018, 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/ `pub run test -N always_specify_type_annotations` + +final x = 1; //LINT [1:5] +final int xx = 3; +const y = 2; //LINT +const int yy = 3; + +a(var x) {} //LINT +b(s) {} //LINT [3:1] +c(int x) {} +d(final x) {} //LINT +e(final int x) {} + +main() { + var x = ''; //LINT [3:3] + for (var i = 0; i < 10; ++i) { //LINT [8:3] + print(i); + } + List ls = []; + ls.forEach((s) => print(s)); //LINT [15:1] + for (var l in ls) { //LINT [8:3] + print(l); + } + try { + for (final l in ls) { // LINT [10:5] + print(l); + } + } on Exception catch (ex) { + print(ex); + } catch (e) { // NO warning (https://codereview.chromium.org/1427223002/) + print(e); + } + + var __; // LINT + + listen((_) { // OK! + // ... + }); +} + +listen(void onData(Object event)) {} + +var z; //LINT + +class Foo { + static var bar; //LINT + static final baz = 1; //LINT + static final int bazz = 42; + var foo; //LINT + Foo(var bar); //LINT [7:3] +} diff --git a/test/rules/always_specify_type_arguments_for_classes.dart b/test/rules/always_specify_type_arguments_for_classes.dart new file mode 100644 index 000000000..a979a7e1d --- /dev/null +++ b/test/rules/always_specify_type_arguments_for_classes.dart @@ -0,0 +1,49 @@ +// Copyright (c) 2018, 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/ `pub run test -N always_specify_type_arguments_for_classes` + +// Hack to work around issues importing `meta.dart` in tests. +// Ideally, remove: +library meta; + +class _OptionalTypeArgs { + const _OptionalTypeArgs(); +} + +const _OptionalTypeArgs optionalTypeArgs = const _OptionalTypeArgs(); + +// ... and replace w/: +// import 'package:meta/meta.dart'; + +Map map = {}; //LINT +List strings = []; //LINT + +List list; // LINT +List lists; //LINT +List ints; //OK + + +@optionalTypeArgs +class P { } + +main() { + var p = new P(); //OK (optionalTypeArgs) +} + +P doSomething(P p) //OK (optionalTypeArgs) +{ + return p; +} + +class Foo { + void f(List l) { } //LINT +} + +void m() { + if ('' is Map) //OK { + { + print("won't happen"); + } +} diff --git a/test/rules/always_specify_type_arguments_for_methods.dart b/test/rules/always_specify_type_arguments_for_methods.dart new file mode 100644 index 000000000..08ba01bb0 --- /dev/null +++ b/test/rules/always_specify_type_arguments_for_methods.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2015, 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/ `pub run test -N always_specify_type_arguments_for_methods` + +// Hack to work around issues importing `meta.dart` in tests. +// Ideally, remove: +library meta; + +class _OptionalTypeArgs { + const _OptionalTypeArgs(); +} + +const _OptionalTypeArgs optionalTypeArgs = const _OptionalTypeArgs(); + +// ... and replace w/: +// import 'package:meta/meta.dart'; + +@optionalTypeArgs +void g() {} + +void test() { + g(); //OK + g(); //OK +} + +class A { + m1() {} + @optionalTypeArgs + m2() {} + // no lint for method with return type and parameters all the same (m3 and m6) + T m3() => null; + R m4() => null; + T m5(int i) => null; + T m6(T i) => null; + + @optionalTypeArgs + static A f() => null; + + m() { + A a; + a.m1(); // LINT + a.m1(); // OK + a.m2(); // OK + a.m2(); // OK + a.m3(); // OK + a.m3(); // OK + a.m4(); // LINT + a.m4(); // OK + a.m5(null); // LINT + a.m5(null); // OK + a.m6(null); // OK + a.m6(null); // OK + A.f(); // OK + A.f(); // OK + } +}