Merge pull request #1789 from tenhobi/add-effective_dart-to-linter-page

Add effective_dart to linter page
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2473bbf..2791f4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+# 0.1.102
+
+* `avoid_web_libraries_in_flutter` updated to disallow access from all but Flutter web plugin packages
+* updated `avoid_returning_null_for_void` to check only `null` literals (and not expressions having `Null` types)
+* fixed `prefer_final_fields` to respect non-mutating prefix operators
+* new lint: `prefer_is_not_operator`
+* new lint: `avoid_unnecessary_containers`
+* added basic nnbd-awareness to `avoid_init_to_null`
+
 # 0.1.101
 
 * fixed `diagnostic_describe_all_properties` to flag properties in `Diagnosticable`s with no debug methods defined
diff --git a/lib/src/rules/always_specify_types.dart b/lib/src/rules/always_specify_types.dart
index ee265bc..77a2a2f 100644
--- a/lib/src/rules/always_specify_types.dart
+++ b/lib/src/rules/always_specify_types.dart
@@ -66,7 +66,7 @@
 /// type args.
 String _OPTIONAL_TYPE_ARGS_VAR_NAME = 'optionalTypeArgs';
 
-bool _isOptionallyParameterized(ParameterizedType type) {
+bool _isOptionallyParameterized(InterfaceType type) {
   List<ElementAnnotation> metadata = type.element?.metadata;
   if (metadata != null) {
     return metadata
@@ -126,7 +126,7 @@
 
   visitNamedType(NamedType namedType) {
     DartType type = namedType.type;
-    if (type is ParameterizedType) {
+    if (type is InterfaceType) {
       if (type.typeParameters.isNotEmpty &&
           namedType.typeArguments == null &&
           namedType.parent is! IsExpression &&
diff --git a/lib/src/rules/avoid_returning_null_for_void.dart b/lib/src/rules/avoid_returning_null_for_void.dart
index 1003014..ec06a90 100644
--- a/lib/src/rules/avoid_returning_null_for_void.dart
+++ b/lib/src/rules/avoid_returning_null_for_void.dart
@@ -77,7 +77,7 @@
   }
 
   void _visit(AstNode node, Expression expression) {
-    if (expression.staticType?.isDartCoreNull != true) {
+    if (expression is! NullLiteral) {
       return;
     }
 
@@ -102,7 +102,7 @@
       rule.reportLint(node);
     } else if (isAsync &&
         type.isDartAsyncFuture &&
-        (type as ParameterizedType).typeArguments.first.isVoid) {
+        (type as InterfaceType).typeArguments.first.isVoid) {
       rule.reportLint(node);
     }
   }
diff --git a/lib/src/rules/avoid_web_libraries_in_flutter.dart b/lib/src/rules/avoid_web_libraries_in_flutter.dart
index 78aa2eb..a2ad24e 100644
--- a/lib/src/rules/avoid_web_libraries_in_flutter.dart
+++ b/lib/src/rules/avoid_web_libraries_in_flutter.dart
@@ -10,19 +10,20 @@
 import '../analyzer.dart';
 import '../ast.dart';
 
-const _desc = r'Avoid using web-only libraries outside Flutter web projects.';
+const _desc =
+    r'Avoid using web-only libraries outside Flutter web plugin packages.';
 
 const _details = r'''Avoid using web libraries, `dart:html`, `dart:js` and 
-`dart:js_util` in non-web Flutter projects.  These libraries are not supported
-outside a web context and functionality that depends on them will fail at
-runtime.
+`dart:js_util` in Flutter packages that are not web plugins. These libraries are 
+not supported outside a web context; functionality that depends on them will
+fail at runtime in Flutter mobile, and their use is generally discouraged in
+Flutter web.
 
-Web library access is allowed in:
+Web library access *is* allowed in:
 
-* projects meant to run on the web (e.g., have a `web/` directory)
 * plugin packages that declare `web` as a supported context
 
-otherwise, imports of `dart:html`, `dart:js` and  `dart:js_util` are flagged.
+otherwise, imports of `dart:html`, `dart:js` and  `dart:js_util` are disallowed.
 ''';
 
 /// todo (pq): consider making a utility and sharing w/ `prefer_relative_imports`
@@ -86,10 +87,10 @@
       return false;
     }
 
-    // Check for a web directory or a web plugin context declaration.
-    return !pubspecFile.parent.getChild('web').exists &&
-        ((parsedPubspec['flutter'] ?? const {})['plugin'] ?? const {})['web'] ==
-            null;
+    // Check for a web plugin context declaration.
+    return ((parsedPubspec['flutter'] ?? const {})['plugin'] ??
+            const {})['web'] ==
+        null;
   }
 
   bool isWebUri(String uri) {
diff --git a/lib/src/rules/prefer_final_fields.dart b/lib/src/rules/prefer_final_fields.dart
index 59cb384..308f543 100644
--- a/lib/src/rules/prefer_final_fields.dart
+++ b/lib/src/rules/prefer_final_fields.dart
@@ -5,6 +5,7 @@
 import 'dart:collection';
 
 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/element.dart';
 
@@ -35,10 +36,8 @@
   var _label = 'hola mundo! GoodMutable', _offender = 'mumble mumble!'; // LINT
   var _someOther; // LINT
 
-
   MultipleMutable() : _someOther = 5;
 
-
   MultipleMutable(this._someOther);
 
   void changeLabel() {
@@ -85,6 +84,19 @@
 ```
 ''';
 
+bool _containedInFormal(Element element, FormalParameter formal) {
+  final formalField = formal.identifier.staticElement;
+  return formalField is FieldFormalParameterElement &&
+      formalField.field == element;
+}
+
+bool _containedInInitializer(
+        Element element, ConstructorInitializer initializer) =>
+    initializer is ConstructorFieldInitializer &&
+    DartTypeUtilities.getCanonicalElementFromIdentifier(
+            initializer.fieldName) ==
+        element;
+
 class PreferFinalFields extends LintRule implements NodeLintRule {
   PreferFinalFields()
       : super(
@@ -121,7 +133,11 @@
 
   @override
   void visitPrefixExpression(PrefixExpression node) {
-    _addMutatedFieldElement(node.operand);
+    final operator = node.operator;
+    if (operator.type == TokenType.MINUS_MINUS ||
+        operator.type == TokenType.PLUS_PLUS) {
+      _addMutatedFieldElement(node.operand);
+    }
     super.visitPrefixExpression(node);
   }
 
@@ -182,16 +198,3 @@
     }
   }
 }
-
-bool _containedInInitializer(
-        Element element, ConstructorInitializer initializer) =>
-    initializer is ConstructorFieldInitializer &&
-    DartTypeUtilities.getCanonicalElementFromIdentifier(
-            initializer.fieldName) ==
-        element;
-
-bool _containedInFormal(Element element, FormalParameter formal) {
-  final formalField = formal.identifier.staticElement;
-  return formalField is FieldFormalParameterElement &&
-      formalField.field == element;
-}
diff --git a/lib/src/rules/void_checks.dart b/lib/src/rules/void_checks.dart
index 7fdbef6..c1470f9 100644
--- a/lib/src/rules/void_checks.dart
+++ b/lib/src/rules/void_checks.dart
@@ -64,7 +64,7 @@
     if (type.isVoid) return true;
     if (type.isDartCoreNull) return true;
     if (type.isDartAsyncFuture &&
-        type is ParameterizedType &&
+        type is InterfaceType &&
         (type.typeArguments.first.isVoid ||
             type.typeArguments.first.isDartCoreNull)) {
       return true;
diff --git a/lib/src/util/dart_type_utilities.dart b/lib/src/util/dart_type_utilities.dart
index 78e1c35..4e7f966 100644
--- a/lib/src/util/dart_type_utilities.dart
+++ b/lib/src/util/dart_type_utilities.dart
@@ -375,17 +375,17 @@
         typeSystem.isSubtypeOf(rightType, leftType)) {
       return false;
     }
-    Element leftElement = leftType.element;
-    Element rightElement = rightType.element;
-    if (leftElement is ClassElement && rightElement is ClassElement) {
-      // In this case, [leftElement] and [rightElement] each represent a class,
-      // like `int` or `List` or `Future<T>` or `Iterable<String>`.
-      if (isClass(leftType, rightElement.name, rightElement.library.name)) {
+    if (leftType is InterfaceType && rightType is InterfaceType) {
+      // In this case, [leftElement] and [rightElement] each represent
+      // the same class, like `int`, or `Iterable<String>`.
+      var leftElement = leftType.element;
+      var rightElement = rightType.element;
+      if (leftElement == rightElement) {
         // In this case, [leftElement] and [rightElement] represent the same
         // class, modulo generics, e.g. `List<int>` and `List<dynamic>`. Now we
         // need to check type arguments.
-        var leftTypeArguments = (leftType as ParameterizedType).typeArguments;
-        var rightTypeArguments = (rightType as ParameterizedType).typeArguments;
+        var leftTypeArguments = leftType.typeArguments;
+        var rightTypeArguments = rightType.typeArguments;
         if (leftTypeArguments.length != rightTypeArguments.length) {
           // I cannot think of how we would enter this block, but it guards
           // against RangeError below.
@@ -405,9 +405,10 @@
         return leftElement.supertype?.isObject == true ||
             leftElement.supertype != rightElement.supertype;
       }
-    } else if (leftElement is TypeParameterElement &&
-        rightElement is TypeParameterElement) {
-      return unrelatedTypes(typeSystem, leftElement.bound, rightElement.bound);
+    } else if (leftType is TypeParameterType &&
+        rightType is TypeParameterType) {
+      return unrelatedTypes(
+          typeSystem, leftType.element.bound, rightType.element.bound);
     } else if (leftType is FunctionType) {
       if (_isFunctionTypeUnrelatedToType(leftType, rightType)) {
         return true;
diff --git a/lib/src/util/flutter_utils.dart b/lib/src/util/flutter_utils.dart
index 99a5f1e..15b8dd9 100644
--- a/lib/src/util/flutter_utils.dart
+++ b/lib/src/util/flutter_utils.dart
@@ -26,7 +26,7 @@
   if (isWidgetType(type)) {
     return true;
   }
-  if (type is ParameterizedType &&
+  if (type is InterfaceType &&
       DartTypeUtilities.implementsAnyInterface(type, _collectionInterfaces)) {
     return type.typeParameters.length == 1 &&
         isWidgetProperty(type.typeArguments.first);
diff --git a/lib/src/version.dart b/lib/src/version.dart
index 7dd531d..ee5dc7d 100644
--- a/lib/src/version.dart
+++ b/lib/src/version.dart
@@ -3,4 +3,4 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /// Package version.  Synchronized w/ pubspec.yaml.
-const String version = '0.1.101';
+const String version = '0.1.102';
diff --git a/pubspec.yaml b/pubspec.yaml
index 779a79a..203c8e8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: linter
-version: 0.1.101
+version: 0.1.102
 
 author: Dart Team <misc@dartlang.org>
 
@@ -23,12 +23,7 @@
 
 dev_dependencies:
   cli_util: ^0.1.2
-  # Update when 1.3.3 lands.
-  # https://github.com/dart-lang/dart_style/issues/866
-  dart_style:
-    git:
-      url: git://github.com/dart-lang/dart_style.git
-      ref: 108a94c8b1797d4f535edff2746bf31b185d04b2
+  dart_style: ^1.3.3
   github: ^5.0.0
   grinder: ^0.8.0
   http: ^0.12.0
diff --git a/test/engine_test.dart b/test/engine_test.dart
index 78be356..591c7ec 100644
--- a/test/engine_test.dart
+++ b/test/engine_test.dart
@@ -40,8 +40,6 @@
 
       _test('exception', 'EXCEPTION: LinterException: foo',
           (r) => r.exception(LinterException('foo')));
-      _test('logError', 'ERROR: foo', (r) => r.logError('foo'));
-      _test('logInformation', 'INFO: foo', (r) => r.logInformation('foo'));
       _test('warn', 'WARN: foo', (r) => r.warn('foo'));
     });
 
@@ -55,32 +53,6 @@
       });
     });
 
-    group('analysis logger', () {
-      var currentErr = errorSink;
-      var currentOut = outSink;
-      var errCollector = CollectingSink();
-      var outCollector = CollectingSink();
-      var logger = StdLogger();
-      setUp(() {
-        errorSink = errCollector;
-        outSink = outCollector;
-      });
-      tearDown(() {
-        errorSink = currentErr;
-        outSink = currentOut;
-        errCollector.buffer.clear();
-        outCollector.buffer.clear();
-      });
-      test('logError', () {
-        logger.logError('logError');
-        expect(errCollector.trim(), 'logError');
-      });
-      test('logInformation', () {
-        logger.logInformation('logInformation');
-        expect(outCollector.trim(), 'logInformation');
-      });
-    });
-
     group('camel case', () {
       test('humanize', () {
         expect(CamelCaseString('FooBar').humanized, 'Foo Bar');
diff --git a/test/integration_test.dart b/test/integration_test.dart
index 369fcea..9166328 100644
--- a/test/integration_test.dart
+++ b/test/integration_test.dart
@@ -70,8 +70,8 @@
           '--rules=avoid_web_libraries_in_flutter',
         ], LinterOptions());
         expect(collectingOut.trim(),
-            contains('2 files analyzed, 0 issues found, in'));
-        expect(exitCode, 0);
+            contains('2 files analyzed, 3 issues found, in'));
+        expect(exitCode, 1);
       });
 
       test('web plugin', () async {
diff --git a/test/rules/prefer_final_fields.dart b/test/rules/prefer_final_fields.dart
index d45456c..25bd514 100644
--- a/test/rules/prefer_final_fields.dart
+++ b/test/rules/prefer_final_fields.dart
@@ -4,7 +4,56 @@
 
 // test w/ `pub run test -N prefer_final_fields`
 
-//ignore_for_file: unused_field, unused_local_variable
+//ignore_for_file: unused_field, unused_local_variable, prefer_expression_function_bodies
+
+class PrefixOps {
+  bool _initialized = false; // LINT
+  int _num = 1; // LINT
+  int _num2 = 1; // OK
+  int _bits = 0xffff; // LINT
+  int getValue() {
+    if (!_initialized) {
+      return 0;
+    }
+    if (-_num  == -1) {
+      return 0;
+    }
+    if (~_bits == 0) {
+      return 0;
+    }
+    if (--_num2  == 0) {
+      return 0;
+    }
+    return 1;
+  }
+}
+
+typedef bool Predicate();
+
+class PostfixOps {
+  int _num = 1; // OK
+  int _num2 = 1; // OK
+  String _string = ''; // LINT
+
+  Predicate _predicate = () => false; // LINT
+
+  int getValue() {
+    if (_num--  == -1) {
+      return 0;
+    }
+    if (_num2++  == 1) {
+      return 0;
+    }
+    if (_predicate()) {
+      return 1;
+    }
+    if (_string.length == 1) {
+      return 0;
+    }
+    return 1;
+  }
+}
+
 class FalsePositiveWhenReturn {
   int _value = 0;
   int getValue() {