Check unsafe attributes and methods on extensions (#2057)

Check unsafe attributes and methods on extensions
diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 393fbeb..04e25d3 100644
--- a/lib/src/rules/unsafe_html.dart
+++ b/lib/src/rules/unsafe_html.dart
@@ -4,6 +4,7 @@
 
 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 '../analyzer.dart';
@@ -53,6 +54,7 @@
     final visitor = _Visitor(this);
     registry.addAssignmentExpression(this, visitor);
     registry.addInstanceCreationExpression(this, visitor);
+    registry.addFunctionExpressionInvocation(this, visitor);
     registry.addMethodInvocation(this, visitor);
   }
 }
@@ -76,27 +78,33 @@
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
     final leftPart = node.leftHandSide.unParenthesized;
-    if (leftPart is PropertyAccess) {
-      _checkAssignment(leftPart.realTarget, leftPart.propertyName, node);
+    if (leftPart is SimpleIdentifier) {
+      var leftPartElement = leftPart.staticElement;
+      if (leftPartElement == null) return;
+      var enclosingElement = leftPartElement.enclosingElement;
+      if (enclosingElement is ClassElement) {
+        _checkAssignment(enclosingElement.thisType, leftPart, node);
+      }
+    } else if (leftPart is PropertyAccess) {
+      _checkAssignment(
+          leftPart.realTarget?.staticType, leftPart.propertyName, node);
     } else if (leftPart is PrefixedIdentifier) {
-      _checkAssignment(leftPart.prefix, leftPart.identifier, node);
+      _checkAssignment(leftPart.prefix?.staticType, leftPart.identifier, node);
     }
   }
 
-  void _checkAssignment(Expression target, SimpleIdentifier property,
+  void _checkAssignment(DartType type, SimpleIdentifier property,
       AssignmentExpression assignment) {
-    if (property == null || target == null) return;
+    if (property == null || type == null) return;
 
     // It is more efficient to check the setter's name before checking whether
     // the target is an interesting type.
     if (property.name == 'href') {
-      final type = target.staticType;
       if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
         rule.reportLint(assignment,
             arguments: ['href'], errorCode: unsafeAttributeCode);
       }
     } else if (property.name == 'src') {
-      final type = target.staticType;
       if (type.isDynamic ||
           type.extendsDartHtmlClass('EmbedElement') ||
           type.extendsDartHtmlClass('IFrameElement') ||
@@ -106,7 +114,6 @@
             arguments: ['src'], errorCode: unsafeAttributeCode);
       }
     } else if (property.name == 'srcdoc') {
-      final type = target.staticType;
       if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
         rule.reportLint(assignment,
             arguments: ['srcdoc'], errorCode: unsafeAttributeCode);
@@ -134,12 +141,26 @@
 
   @override
   void visitMethodInvocation(MethodInvocation node) {
-    var type = node.target?.staticType;
-    if (type == null) return;
-
     var methodName = node.methodName?.name;
     if (methodName == null) return;
 
+    // The static type of the target.
+    DartType type;
+    if (node.realTarget == null) {
+      // Implicit `this` target.
+      var methodElement = node.methodName.staticElement;
+      if (methodElement == null) return;
+      var enclosingElement = methodElement.enclosingElement;
+      if (enclosingElement is ClassElement) {
+        type = enclosingElement.thisType;
+      } else {
+        return;
+      }
+    } else {
+      type = node.realTarget.staticType;
+      if (type == null) return;
+    }
+
     if (methodName == 'createFragment' &&
         (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
       rule.reportLint(node,
diff --git a/test/rules/unsafe_html.dart b/test/rules/unsafe_html.dart
index b8d94bc..7224978 100644
--- a/test/rules/unsafe_html.dart
+++ b/test/rules/unsafe_html.dart
@@ -29,9 +29,12 @@
 
   var heading = HeadingElement.h1();
   heading.createFragment('<script>'); // LINT
+  heading..createFragment('<script>'); // LINT
   heading.setInnerHtml('<script>'); // LINT
+  heading..setInnerHtml('<script>'); // LINT
 
   Window().open('url', 'name'); // LINT
+  Window()..open('url', 'name'); // LINT
 
   DocumentFragment.html('<script>'); // LINT
   Element.html('<script>'); // LINT
@@ -72,3 +75,37 @@
 
   void setInnerHtml(String html) {}
 }
+
+extension on ScriptElement {
+  void sneakySetSrc1(String url) => src = url; // LINT
+  void sneakySetSrc2(String url) => this.src = url; // LINT
+
+  // TODO(srawlins): Re-enable this when analyzer's mock SDK includes
+  // `Element.createFragment`.
+  // void sneakyCreateFragment1(String html) => createFragment(html); // LINT
+  void sneakyCreateFragment2(String html) => this.createFragment(html); // LINT
+
+  // TODO(srawlins): Re-enable this when analyzer's mock SDK includes
+  // `Element.setInnerHtml`.
+  // void sneakySetInnerHtml1(String html) => setInnerHtml(html); // LINT
+  void sneakySetInnerHtml2(String html) => this.setInnerHtml(html); // LINT
+}
+
+extension on Window {
+  void sneakyOpen1(String url, String name) => open(url, name); // LINT
+  void sneakyOpen2(String url, String name) => this.open(url, name); // LINT
+}
+
+extension on C {
+  void sneakySetSrc1(String url) => src = url; // OK
+  void sneakySetSrc2(String url) => this.src = url; // OK
+
+  void sneakyCreateFragment1(String html) => createFragment(html); // OK
+  void sneakyCreateFragment2(String html) => this.createFragment(html); // OK
+
+  void sneakySetInnerHtml1(String html) => setInnerHtml(html); // OK
+  void sneakySetInnerHtml2(String html) => this.setInnerHtml(html); // OK
+
+  void sneakyOpen1(String url, String name) => open(url, name); // OK
+  void sneakyOpen2(String url, String name) => this.open(url, name); // OK
+}