Merge pull request #638 from JPaulsen/fiX_linted_node_avoid_types_on_closure_parameters

Fix linted node: avoid_types_on_closure_parameters
diff --git a/lib/src/rules/cascade_invocations.dart b/lib/src/rules/cascade_invocations.dart
index 98296c9..5ed2654 100644
--- a/lib/src/rules/cascade_invocations.dart
+++ b/lib/src/rules/cascade_invocations.dart
@@ -9,6 +9,7 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:linter/src/analyzer.dart';
+import 'package:linter/src/util/dart_type_utilities.dart';
 
 const _desc = r'Cascade consecutive method invocations on the same reference.';
 
@@ -53,30 +54,49 @@
 
 ''';
 
-SimpleIdentifier _findTarget(AstNode node) {
-  Expression expression;
-  if (node is ExpressionStatement) {
-    expression = node.expression;
+Element _getElementFromVariableDeclarationStatement(
+    VariableDeclarationStatement statement) {
+  final variables = statement.variables.variables;
+  if (variables.length == 1) {
+    return variables.first.element;
   }
-
-  if (expression is MethodInvocation &&
-      expression.realTarget is SimpleIdentifier) {
-    return expression.realTarget;
-  }
-
-  if (expression is PrefixedIdentifier &&
-      expression.prefix is SimpleIdentifier) {
-    return expression.prefix;
-  }
-
-  if (expression is AssignmentExpression &&
-      expression.leftHandSide is PrefixedIdentifier) {
-    return (expression.leftHandSide as PrefixedIdentifier).prefix;
-  }
-
   return null;
 }
 
+ExecutableElement _getExecutableElementFromMethodInvocation(
+    MethodInvocation node) {
+  if (_isInvokedWithoutNullAwareOperator(node.operator)) {
+    final executableElement =
+        DartTypeUtilities.getCanonicalElementFromIdentifier(node.methodName);
+    if (executableElement is ExecutableElement) {
+      return executableElement;
+    }
+  }
+  return null;
+}
+
+Element _getPrefixElementFromExpression(Expression rawExpression) {
+  final expression = rawExpression.unParenthesized;
+  if (expression is PrefixedIdentifier) {
+    return DartTypeUtilities
+        .getCanonicalElementFromIdentifier(expression.prefix);
+  } else if (expression is PropertyAccess &&
+      _isInvokedWithoutNullAwareOperator(expression.operator)) {
+    return DartTypeUtilities
+        .getCanonicalElementFromIdentifier(expression.target);
+  }
+  return null;
+}
+
+Element _getTargetElementFromCascadeExpression(CascadeExpression node) =>
+    DartTypeUtilities.getCanonicalElementFromIdentifier(node.target);
+
+Element _getTargetElementFromMethodInvocation(MethodInvocation node) =>
+    DartTypeUtilities.getCanonicalElementFromIdentifier(node.target);
+
+bool _isInvokedWithoutNullAwareOperator(Token token) =>
+    token?.type == TokenType.PERIOD;
+
 /// Rule to lint consecutive invocations of methods or getters on the same
 /// reference that could be done with the cascade operator.
 class CascadeInvocations extends LintRule {
@@ -96,57 +116,161 @@
   AstVisitor getVisitor() => _visitor;
 }
 
-class _Visitor extends SimpleAstVisitor {
-  final LintRule rule;
-  _Visitor(this.rule);
+/// A CascadableExpression is an object that is built from an expression and
+/// knows if it is able to join to another CascadableExpression.
+class _CascadableExpression {
+  static final NULL_CASCADABLE_EXPRESSION = new _CascadableExpression._internal(
+      null, [],
+      canJoin: false, canReceive: false, canBeCascaded: false);
 
-  @override
-  void visitExpressionStatement(ExpressionStatement node) {
-    final prefixIdentifier = _findTarget(node);
-    if (prefixIdentifier == null || node.parent is! Block) {
-      return;
-    }
+  final bool canJoin;
+  final bool canReceive;
+  final bool canBeCascaded;
 
-    final Block body = node.parent;
-    final previousNodes = body.statements.takeWhile((n) => n != node);
-    if (previousNodes.isEmpty) {
-      return;
-    }
+  /// This is necessary when you have a variable declaration so that element
+  /// is critical and it can't be used as a parameter of a method invocation or
+  /// in the right part of an assignment in a following expression that we would
+  /// like to join to this.
+  final bool isCritical;
+  final Element element;
+  final List<AstNode> criticalNodes;
 
-    final previousNode = previousNodes.last;
-    final SimpleIdentifier previousIdentifier = _findTarget(previousNode);
-    if (previousIdentifier != null &&
-        previousIdentifier.staticElement == prefixIdentifier.staticElement &&
-        previousIdentifier.staticElement is! PrefixElement &&
-        previousIdentifier.bestElement is! ClassElement) {
-      rule.reportLint(node);
+  factory _CascadableExpression.fromExpressionStatement(
+      ExpressionStatement statement) {
+    final expression = statement.expression.unParenthesized;
+    if (expression is AssignmentExpression) {
+      return new _CascadableExpression._fromAssignmentExpression(expression);
     }
-
-    if (previousNode is VariableDeclarationStatement &&
-        _isInvokedWithoutNullAwareOperator(node)) {
-      _reportIfDeclarationFollowedByMethodInvocation(
-          previousNode, prefixIdentifier, node);
+    if (expression is MethodInvocation) {
+      return new _CascadableExpression._fromMethodInvocation(expression);
     }
+    if (expression is CascadeExpression) {
+      return new _CascadableExpression._fromCascadeExpression(expression);
+    }
+    if (expression is PropertyAccess &&
+        _isInvokedWithoutNullAwareOperator(expression.operator)) {
+      return new _CascadableExpression._fromPropertyAccess(expression);
+    }
+    if (expression is PrefixedIdentifier) {
+      return new _CascadableExpression._fromPrefixedIdentifier(expression);
+    }
+    return NULL_CASCADABLE_EXPRESSION;
   }
 
-  void _reportIfDeclarationFollowedByMethodInvocation(
-      VariableDeclarationStatement variablesDeclaration,
-      SimpleIdentifier simpleIdentifier,
-      AstNode node) {
-    final variables = variablesDeclaration.variables.variables;
-    if (variables.length == 1 &&
-        variables.first.name.staticElement == simpleIdentifier.staticElement) {
-      rule.reportLint(node);
+  factory _CascadableExpression.fromVariableDeclarationStatement(
+      VariableDeclarationStatement node) {
+    final element = _getElementFromVariableDeclarationStatement(node);
+    return new _CascadableExpression._internal(element, [],
+        canJoin: false,
+        canReceive: true,
+        canBeCascaded: false,
+        isCritical: true);
+  }
+
+  factory _CascadableExpression._fromAssignmentExpression(
+      AssignmentExpression node) {
+    final leftExpression = node.leftHandSide.unParenthesized;
+    if (leftExpression is SimpleIdentifier) {
+      return new _CascadableExpression._internal(
+          DartTypeUtilities.getCanonicalElement(leftExpression.bestElement),
+          [node.rightHandSide],
+          canJoin: false,
+          canReceive: true,
+          canBeCascaded: false,
+          isCritical: true);
     }
+    // setters
+    return new _CascadableExpression._internal(
+        _getPrefixElementFromExpression(node.leftHandSide),
+        [node.rightHandSide],
+        canJoin: true,
+        canReceive: true,
+        canBeCascaded: true);
+  }
+
+  factory _CascadableExpression._fromCascadeExpression(
+          CascadeExpression node) =>
+      new _CascadableExpression._internal(
+          _getTargetElementFromCascadeExpression(node), node.cascadeSections,
+          canJoin: true, canReceive: true, canBeCascaded: true);
+
+  factory _CascadableExpression._fromMethodInvocation(MethodInvocation node) {
+    final executableElement = _getExecutableElementFromMethodInvocation(node);
+    bool isNonStatic = executableElement?.isStatic == false;
+    if (isNonStatic) {
+      return new _CascadableExpression._internal(
+          _getTargetElementFromMethodInvocation(node), [node.argumentList],
+          canJoin: true, canReceive: true, canBeCascaded: true);
+    }
+    return NULL_CASCADABLE_EXPRESSION;
+  }
+
+  factory _CascadableExpression._fromPrefixedIdentifier(
+      PrefixedIdentifier node) {
+    return new _CascadableExpression._internal(
+        DartTypeUtilities.getCanonicalElementFromIdentifier(node.prefix), [],
+        canJoin: true, canReceive: true, canBeCascaded: true);
+  }
+
+  factory _CascadableExpression._fromPropertyAccess(PropertyAccess node) {
+    return new _CascadableExpression._internal(
+        DartTypeUtilities.getCanonicalElementFromIdentifier(node.target), [],
+        canJoin: true, canReceive: true, canBeCascaded: true);
+  }
+
+  _CascadableExpression._internal(this.element, this.criticalNodes,
+      {this.canJoin,
+      this.canReceive,
+      this.canBeCascaded,
+      this.isCritical: false});
+
+  bool compatibleWith(_CascadableExpression expressionBox) =>
+      element != null &&
+      expressionBox.canReceive &&
+      canJoin &&
+      (canBeCascaded || expressionBox.canBeCascaded) &&
+      element == expressionBox.element &&
+      !_hasCriticalDependencies(expressionBox);
+
+  bool _hasCriticalDependencies(_CascadableExpression expressionBox) {
+    bool _isCriticalNode(AstNode node) =>
+        DartTypeUtilities.getCanonicalElementFromIdentifier(node) ==
+        expressionBox.element;
+    return expressionBox.isCritical &&
+        criticalNodes.any((node) =>
+            _isCriticalNode(node) ||
+            DartTypeUtilities.traverseNodesInDFS(node).any(_isCriticalNode));
   }
 }
 
-bool _isInvokedWithoutNullAwareOperator(AstNode node) {
-  if (node is ExpressionStatement && node.expression is MethodInvocation) {
-    final tokenType = (node.expression as MethodInvocation).operator.type;
-    return tokenType == TokenType.PERIOD ||
-        tokenType == TokenType.PERIOD_PERIOD;
-  }
+class _Visitor extends SimpleAstVisitor {
+  final LintRule rule;
 
-  return false;
+  _Visitor(this.rule);
+
+  @override
+  visitBlock(Block node) {
+    if (node.statements.length < 2) {
+      return;
+    }
+    var previousExpressionBox =
+        _CascadableExpression.NULL_CASCADABLE_EXPRESSION;
+    for (final statement in node.statements) {
+      var currentExpressionBox =
+          _CascadableExpression.NULL_CASCADABLE_EXPRESSION;
+      if (statement is VariableDeclarationStatement) {
+        currentExpressionBox =
+            new _CascadableExpression.fromVariableDeclarationStatement(
+                statement);
+      }
+      if (statement is ExpressionStatement) {
+        currentExpressionBox =
+            new _CascadableExpression.fromExpressionStatement(statement);
+      }
+      if (currentExpressionBox.compatibleWith(previousExpressionBox)) {
+        rule.reportLint(statement);
+      }
+      previousExpressionBox = currentExpressionBox;
+    }
+  }
 }
diff --git a/test/rules/cascade_invocations.dart b/test/rules/cascade_invocations.dart
index 1065506..bf4e79a 100644
--- a/test/rules/cascade_invocations.dart
+++ b/test/rules/cascade_invocations.dart
@@ -6,9 +6,16 @@
 
 import 'dart:math' as math;
 
+void alreadyDeclared() {
+  List list;
+  print("intermediate statement");
+  list = [];
+  list.clear(); // LINT
+}
+
 void noCascade() {
   List<int> list = [];
-  list.removeWhere((i) => i % 5 == 0); // LINT
+  list.clear(); // LINT
   list.clear(); // LINT
 }
 
@@ -17,15 +24,27 @@
 void noCascadeIntermediate() {
   List<int> list = [];
   print(list);
-  list.removeWhere((i) => i % 5 == 0);
+  list.clear();
   list.clear(); // LINT
+  list.toString(); // LINT
+  // toString is not void and it is not a problem.
+}
+
+void cascadeIntermediate() {
+  List<int> list = [];
+  print(list);
+  list.clear();
+  list.clear(); // LINT
+  list..clear(); // LINT
+  list..clear(); // LINT
+  list..clear(); // LINT
 }
 
 class HasMethodWithNoCascade {
   List<int> list = [];
 
   void noCascade() {
-    list.removeWhere((i) => i % 5 == 0);
+    list.clear();
     list.clear(); // LINT
   }
 }
@@ -43,6 +62,9 @@
   foo.bar; // LINT
   foo.foo(); // LINT
   foo.bar = 8; // LINT
+  foo?.bar = 8; // OK
+  foo.bar = 0; // OK
+  foo?.bar; // OK
 }
 
 void alternatingReferences() {
@@ -68,6 +90,8 @@
 void cascade() {
   final foo = new Foo();
   foo?.baz();
+  foo.baz(); // OK
+  foo.baz(); // LINT
 }
 
 void prefixLibrary() {
@@ -80,8 +104,60 @@
   static int decrement() => 0;
 }
 
-  void cascadeStatic() {
-    StaticFoo.increment();
-    StaticFoo.decrement();
-  }
+void cascadeStatic() {
+  StaticFoo.increment();
+  StaticFoo.decrement();
+}
+
+// Bug 339
+class Identifier339 {
+  String value;
+  String system;
+}
+
+class Resource339 {
+  String id;
+  dynamic identifier;
+}
+
+class Practitioner339 extends Resource339{
+  int foo;
+
+  String build() => null;
+
+  void bar(dynamic) {}
+}
+
+class Entry339 {
+  Resource339 resource;
+}
+
+void main339() {
+  final entry = new Entry339();
+  final resource = (entry.resource as Practitioner339);
+  resource.identifier = [ // OK
+    new Identifier339()
+      ..system = 'urn:system'
+      ..value = 'mdc_${resource.id}'
+  ];
+}
+
+void otherDependencies1() {
+  final entry = new Entry339();
+  final resource = (entry.resource as Practitioner339);
+  resource.identifier = resource; // OK
+}
+
+void otherDependencies2() {
+  final entry = new Entry339();
+  final resource = (entry.resource as Practitioner339);
+  resource.bar(resource); // OK
+}
+
+// assignment
+void otherDependencies3() {
+  var entry, resource;
+  entry = new Entry339();
+  resource = (entry.resource as Practitioner339);
+  resource.bar(resource); // OK
 }