Add three new unsafe APIs to the unsafe_html rule (#2054)

diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 7cba025..32c7a74 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/type.dart';
 
 import '../analyzer.dart';
 import '../util/dart_type_utilities.dart';
@@ -13,9 +14,14 @@
 
 const _details = r'''
 
-**AVOID** assigning directly to the src field of an EmbedElement,
-IFrameElement, ImageElement, or ScriptElement, or the href field of an
-AnchorElement.
+**AVOID**
+
+* assigning directly to the `href` field of an AnchorElement
+* assigning directly to the `src` field of an EmbedElement, IFrameElement,
+  ImageElement, or ScriptElement
+* assigning directly to the `srcdoc` field of an IFrameElement
+* calling the `createFragment` method of Element
+* calling the `setInnerHtml` method of Element
 
 
 **BAD:**
@@ -24,6 +30,12 @@
 ```
 ''';
 
+extension on DartType {
+  /// Returns whether this type extends [className] from the dart:html library.
+  bool extendsDartHtmlClass(String className) =>
+      DartTypeUtilities.extendsClass(this, className, 'dart.dom.html');
+}
+
 class UnsafeHtml extends LintRule implements NodeLintRule {
   UnsafeHtml()
       : super(
@@ -37,6 +49,7 @@
       NodeLintRegistry registry, LinterContext context) {
     final visitor = _Visitor(this);
     registry.addAssignmentExpression(this, visitor);
+    registry.addMethodInvocation(this, visitor);
   }
 }
 
@@ -45,6 +58,12 @@
       LintCode('unsafe_html', '$_descPrefix (assigning "href" attribute).');
   static const srcAttributeCode =
       LintCode('unsafe_html', '$_descPrefix (assigning "src" attribute).');
+  static const srcdocAttributeCode =
+      LintCode('unsafe_html', '$_descPrefix (assigning "srcdoc" attribute).');
+  static const createFragmentMethodCode = LintCode('unsafe_html',
+      '$_descPrefix (calling the "createFragment" method of Element).');
+  static const setInnerHtmlMethodCode = LintCode('unsafe_html',
+      '$_descPrefix (calling the "setInnerHtml" method of Element).');
 
   final LintRule rule;
 
@@ -64,28 +83,44 @@
       AssignmentExpression assignment) {
     if (property == null || target == null) return;
 
-    // It is more efficient to first check if `src` (or `href`) is being
-    // assigned, _then_ check if the target of an interesting  type.
-    if (property.name == 'src') {
+    // 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 ||
-          DartTypeUtilities.extendsClass(
-              type, 'EmbedElement', 'dart.dom.html') ||
-          DartTypeUtilities.extendsClass(
-              type, 'IFrameElement', 'dart.dom.html') ||
-          DartTypeUtilities.extendsClass(
-              type, 'ImageElement', 'dart.dom.html') ||
-          DartTypeUtilities.extendsClass(
-              type, 'ScriptElement', 'dart.dom.html')) {
-        rule.reportLint(assignment, errorCode: srcAttributeCode);
-      }
-    } else if (property.name == 'href') {
-      final type = target.staticType;
-      if (type.isDynamic ||
-          DartTypeUtilities.extendsClass(
-              type, 'AnchorElement', 'dart.dom.html')) {
+      if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
         rule.reportLint(assignment, errorCode: hrefAttributeCode);
       }
+    } else if (property.name == 'src') {
+      final type = target.staticType;
+      if (type.isDynamic ||
+          type.extendsDartHtmlClass('EmbedElement') ||
+          type.extendsDartHtmlClass('IFrameElement') ||
+          type.extendsDartHtmlClass('ImageElement') ||
+          type.extendsDartHtmlClass('ScriptElement')) {
+        rule.reportLint(assignment, errorCode: srcAttributeCode);
+      }
+    } else if (property.name == 'srcdoc') {
+      final type = target.staticType;
+      if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
+        rule.reportLint(assignment, errorCode: srcdocAttributeCode);
+      }
+    }
+  }
+
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    var type = node.target?.staticType;
+    if (type == null) return;
+
+    var methodName = node.methodName?.name;
+    if (methodName == null) return;
+
+    if (methodName == 'createFragment' &&
+        (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
+      rule.reportLint(node, errorCode: createFragmentMethodCode);
+    } else if (methodName == 'setInnerHtml' &&
+        (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
+      rule.reportLint(node, errorCode: setInnerHtmlMethodCode);
     }
   }
 }
diff --git a/test/rules/unsafe_html.dart b/test/rules/unsafe_html.dart
index 0dbae82..b0340da 100644
--- a/test/rules/unsafe_html.dart
+++ b/test/rules/unsafe_html.dart
@@ -25,18 +25,37 @@
     ..type = 'application/javascript';
   script?.src = 'foo.js'; // LINT
 
+  IFrameElement()..srcdoc = 'foo'; // LINT
+
+  var heading = HeadingElement();
+  heading.createFragment('<script>'); // LINT
+  heading.setInnerHtml('<script>'); // LINT
+
   C().src = 'foo.js'; // OK
-  C()..src = 'foo.js'; // OK
-  C()?.src = 'foo.js'; // OK
+  var c = C();
+  c..src = 'foo.js'; // OK
+  c?.src = 'foo.js'; // OK
+  c.srcdoc = 'foo.js'; // OK
+  c.createFragment('<script>'); // OK
+  c.setInnerHtml('<script>'); // OK
 
   dynamic d;
   d.src = 'foo.js'; // LINT
+  d.srcdoc = 'foo.js'; // LINT
   d.href = 'foo.js'; // LINT
+  d.createFragment('<script>'); // LINT
+  d.setInnerHtml('<script>'); // LINT
   (script as dynamic).src = 'foo.js'; // LINT
   (C() as dynamic).src = 'foo.js'; // LINT
 }
 
 class C {
   String src;
+  String srcdoc;
   String href;
+
+  void createFragment(String html) {}
+
+  void setInnerHtml(String html) {}
 }
+