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

Add three more new unsafe APIs to the unsafe_html rule
diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 32c7a74..393fbeb 100644
--- a/lib/src/rules/unsafe_html.dart
+++ b/lib/src/rules/unsafe_html.dart
@@ -21,7 +21,10 @@
   ImageElement, or ScriptElement
 * assigning directly to the `srcdoc` field of an IFrameElement
 * calling the `createFragment` method of Element
+* calling the `open` method of Window
 * calling the `setInnerHtml` method of Element
+* calling the `Element.html` constructor
+* calling the `DocumentFragment.html` constructor
 
 
 **BAD:**
@@ -49,21 +52,22 @@
       NodeLintRegistry registry, LinterContext context) {
     final visitor = _Visitor(this);
     registry.addAssignmentExpression(this, visitor);
+    registry.addInstanceCreationExpression(this, visitor);
     registry.addMethodInvocation(this, visitor);
   }
 }
 
 class _Visitor extends SimpleAstVisitor<void> {
-  static const hrefAttributeCode =
-      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).');
+  // TODO(srawlins): Reference attributes ('href', 'src', and 'srcdoc') with
+  // single-quotes to match the convention in the analyzer and linter packages.
+  // This requires some coordination within Google, as various allow-lists are
+  // keyed on the exact text of the LintCode message.
+  static const unsafeAttributeCode =
+      LintCode('unsafe_html', '$_descPrefix (assigning "{0}" attribute).');
+  static const unsafeMethodCode = LintCode(
+      'unsafe_html', "$_descPrefix (calling the '{0}' method of {1}).");
+  static const unsafeConstructorCode = LintCode(
+      'unsafe_html', "$_descPrefix (calling the '{0}' constructor of {1}).");
 
   final LintRule rule;
 
@@ -88,7 +92,8 @@
     if (property.name == 'href') {
       final type = target.staticType;
       if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
-        rule.reportLint(assignment, errorCode: hrefAttributeCode);
+        rule.reportLint(assignment,
+            arguments: ['href'], errorCode: unsafeAttributeCode);
       }
     } else if (property.name == 'src') {
       final type = target.staticType;
@@ -97,12 +102,32 @@
           type.extendsDartHtmlClass('IFrameElement') ||
           type.extendsDartHtmlClass('ImageElement') ||
           type.extendsDartHtmlClass('ScriptElement')) {
-        rule.reportLint(assignment, errorCode: srcAttributeCode);
+        rule.reportLint(assignment,
+            arguments: ['src'], errorCode: unsafeAttributeCode);
       }
     } else if (property.name == 'srcdoc') {
       final type = target.staticType;
       if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
-        rule.reportLint(assignment, errorCode: srcdocAttributeCode);
+        rule.reportLint(assignment,
+            arguments: ['srcdoc'], errorCode: unsafeAttributeCode);
+      }
+    }
+  }
+
+  @override
+  void visitInstanceCreationExpression(InstanceCreationExpression node) {
+    var type = node.staticType;
+    if (type == null) return;
+
+    var constructorName = node.constructorName;
+    if (constructorName?.name?.name == 'html') {
+      if (type.extendsDartHtmlClass('DocumentFragment')) {
+        rule.reportLint(node,
+            arguments: ['html', 'DocumentFragment'],
+            errorCode: unsafeConstructorCode);
+      } else if (type.extendsDartHtmlClass('Element')) {
+        rule.reportLint(node,
+            arguments: ['html', 'Element'], errorCode: unsafeConstructorCode);
       }
     }
   }
@@ -117,10 +142,17 @@
 
     if (methodName == 'createFragment' &&
         (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
-      rule.reportLint(node, errorCode: createFragmentMethodCode);
+      rule.reportLint(node,
+          arguments: ['createFragment', 'Element'],
+          errorCode: unsafeMethodCode);
     } else if (methodName == 'setInnerHtml' &&
         (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
-      rule.reportLint(node, errorCode: setInnerHtmlMethodCode);
+      rule.reportLint(node,
+          arguments: ['setInnerHtml', 'Element'], errorCode: unsafeMethodCode);
+    } else if (methodName == 'open' &&
+        (type.isDynamic || type.extendsDartHtmlClass('Window'))) {
+      rule.reportLint(node,
+          arguments: ['open', 'Window'], errorCode: unsafeMethodCode);
     }
   }
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index 951dedc..d46fc41 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -12,7 +12,7 @@
   sdk: '>=2.6.0 <3.0.0'
 
 dependencies:
-  analyzer: ^0.39.3
+  analyzer: ^0.39.6
   args: '>=1.4.0 <2.0.0'
   charcode: ^1.0.0
   glob: ^1.0.3
diff --git a/test/rules/unsafe_html.dart b/test/rules/unsafe_html.dart
index b0340da..b8d94bc 100644
--- a/test/rules/unsafe_html.dart
+++ b/test/rules/unsafe_html.dart
@@ -27,23 +27,31 @@
 
   IFrameElement()..srcdoc = 'foo'; // LINT
 
-  var heading = HeadingElement();
+  var heading = HeadingElement.h1();
   heading.createFragment('<script>'); // LINT
   heading.setInnerHtml('<script>'); // LINT
 
+  Window().open('url', 'name'); // LINT
+
+  DocumentFragment.html('<script>'); // LINT
+  Element.html('<script>'); // LINT
+
   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.open('url', 'name'); // OK
   c.setInnerHtml('<script>'); // OK
+  C.html('<script>'); // OK
 
   dynamic d;
   d.src = 'foo.js'; // LINT
   d.srcdoc = 'foo.js'; // LINT
   d.href = 'foo.js'; // LINT
   d.createFragment('<script>'); // LINT
+  d.open('url', 'name'); // LINT
   d.setInnerHtml('<script>'); // LINT
   (script as dynamic).src = 'foo.js'; // LINT
   (C() as dynamic).src = 'foo.js'; // LINT
@@ -54,8 +62,13 @@
   String srcdoc;
   String href;
 
+  C();
+
+  C.html(String content);
+
   void createFragment(String html) {}
 
+  void open(String url, String name) {}
+
   void setInnerHtml(String html) {}
 }
-