Small changes to invalid_runtime_checks_with_js_interop_types

* Make top-level functions private.
* Make `isFromLibrary` an extension method, which makes for some
  nice readability.
* Remove the `typeProvider` field from the `_Visitor` class.
* Null-check some values before passing to `getInvalidJsInteropTypeTest`
  which reduces the number of following null-asserts.
* Don't convert DartTypes into strings when passing to `reportLint` as
  there is logic in there to qualify type names with library paths if
  they have the same name.

Change-Id: Ifc0eab77fef774bebd22779e336457223c31ae7c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/389420
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Auto-Submit: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
diff --git a/pkg/linter/lib/src/rules/invalid_runtime_check_with_js_interop_types.dart b/pkg/linter/lib/src/rules/invalid_runtime_check_with_js_interop_types.dart
index 9afe1e5..d01f661 100644
--- a/pkg/linter/lib/src/rules/invalid_runtime_check_with_js_interop_types.dart
+++ b/pkg/linter/lib/src/rules/invalid_runtime_check_with_js_interop_types.dart
@@ -7,7 +7,6 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/dart/element/type_provider.dart';
 // ignore: implementation_imports
 import 'package:analyzer/src/dart/element/type.dart';
 // ignore: implementation_imports
@@ -32,74 +31,33 @@
   'dart:web_gl',
 };
 
-/// If [type] is a type declared using `@staticInterop` through
-/// `dart:js_interop`, returns the JS type equivalent for that class, which is
-/// just `JSObject`.
-///
-/// `@staticInterop` types that were declared using `package:js` do not apply as
-/// that package is incompatible with dart2wasm.
-///
-/// Returns null if `type` is not a `dart:js_interop` `@staticInterop` class.
-DartType? getJsTypeForStaticInterop(InterfaceType type) {
-  var element = type.element;
-  if (element is! ClassElement) return null;
-  var metadata = element.metadata;
-  var hasJS = false;
-  var hasStaticInterop = false;
-  late LibraryElement dartJsInterop;
-  for (var i = 0; i < metadata.length; i++) {
-    var annotation = metadata[i];
-    var annotationElement = annotation.element;
-    if (annotationElement is ConstructorElement &&
-        isFromLibrary(annotationElement.library, _dartJsInteropUri) &&
-        annotationElement.enclosingElement3.name == 'JS') {
-      hasJS = true;
-      dartJsInterop = annotationElement.library;
-    } else if (annotationElement is PropertyAccessorElement &&
-        isFromLibrary(annotationElement.library, _dartJsAnnotationsUri) &&
-        annotationElement.name == 'staticInterop') {
-      hasStaticInterop = true;
-    }
-  }
-  return (hasJS && hasStaticInterop)
-      ? dartJsInterop.units.single.extensionTypes
-          .singleWhere((extType) => extType.name == 'JSObject')
-          // Nullability is ignored in this lint, so just return `thisType`.
-          .thisType
-      : null;
-}
-
 /// Given a [type] erased by [EraseNonJSInteropTypes], determine if it is a type
 /// that is a `dart:js_interop` type or is bound to one.
-bool isDartJsInteropType(DartType type) {
-  if (type is TypeParameterType) return isDartJsInteropType(type.bound);
+bool _isDartJsInteropType(DartType type) {
+  if (type is TypeParameterType) return _isDartJsInteropType(type.bound);
   if (type is InterfaceType) {
     var element = type.element;
     if (element is ExtensionTypeElement) {
-      return isFromLibrary(element.library, _dartJsInteropUri);
+      return element.isFromLibrary(_dartJsInteropUri);
     }
   }
   return false;
 }
 
-bool isFromLibrary(LibraryElement elementLibrary, String uri) =>
-    elementLibrary.definingCompilationUnit.source ==
-    elementLibrary.context.sourceFactory.forUri(uri);
-
 /// Whether [type] is a user JS interop type using `dart:js_interop` or is bound
 /// to one.
 ///
 /// An interop type is a user interop type if it's an extension type on another
 /// interop type or a `dart:js_interop` `@staticInterop` type.
-bool isUserJsInteropType(DartType type) {
-  if (type is TypeParameterType) return isUserJsInteropType(type.bound);
+bool _isUserJsInteropType(DartType type) {
+  if (type is TypeParameterType) return _isUserJsInteropType(type.bound);
   if (type is InterfaceType) {
     var element = type.element;
     if (element is ExtensionTypeElement) {
       var representationType = element.representation.type;
-      return isDartJsInteropType(representationType) ||
-          isUserJsInteropType(representationType);
-    } else if (getJsTypeForStaticInterop(type) != null) {
+      return _isDartJsInteropType(representationType) ||
+          _isUserJsInteropType(representationType);
+    } else if (_jsTypeForStaticInterop(type) != null) {
       return true;
     }
   }
@@ -115,17 +73,55 @@
 /// Since dart2wasm doesn't support these libraries, users can't come across
 /// platform-inconsistent type tests, and therefore we should not lint for these
 /// types.
-bool isWasmIncompatibleJsInterop(DartType type) {
-  if (type is TypeParameterType) return isWasmIncompatibleJsInterop(type.bound);
+bool _isWasmIncompatibleJsInterop(DartType type) {
+  if (type is TypeParameterType) {
+    return _isWasmIncompatibleJsInterop(type.bound);
+  }
   if (type is! InterfaceType) return false;
   var element = type.element;
   // `hasJS` only checks for the `dart:_js_annotations` definition, which is
   // what we want here.
   if (element.hasJS) return true;
-  return _sdkWebLibraries.any((uri) => isFromLibrary(element.library, uri)) ||
+  return _sdkWebLibraries.any((uri) => element.isFromLibrary(uri)) ||
       // While a type test with types from this library is very rare, we should
       // still ignore it for consistency.
-      isFromLibrary(element.library, _dartJsUri);
+      element.isFromLibrary(_dartJsUri);
+}
+
+/// If [type] is a type declared using `@staticInterop` through
+/// `dart:js_interop`, returns the JS type equivalent for that class, which is
+/// just `JSObject`.
+///
+/// `@staticInterop` types that were declared using `package:js` do not apply as
+/// that package is incompatible with dart2wasm.
+///
+/// Returns null if `type` is not a `dart:js_interop` `@staticInterop` class.
+DartType? _jsTypeForStaticInterop(InterfaceType type) {
+  var element = type.element;
+  if (element is! ClassElement) return null;
+  var metadata = element.metadata;
+  var hasJS = false;
+  var hasStaticInterop = false;
+  late LibraryElement dartJsInterop;
+  for (var annotation in metadata) {
+    var annotationElement = annotation.element;
+    if (annotationElement is ConstructorElement &&
+        annotationElement.isFromLibrary(_dartJsInteropUri) &&
+        annotationElement.enclosingElement3.name == 'JS') {
+      hasJS = true;
+      dartJsInterop = annotationElement.library;
+    } else if (annotationElement is PropertyAccessorElement &&
+        annotationElement.isFromLibrary(_dartJsAnnotationsUri) &&
+        annotationElement.name == 'staticInterop') {
+      hasStaticInterop = true;
+    }
+  }
+  return (hasJS && hasStaticInterop)
+      ? dartJsInterop.units.single.extensionTypes
+          .singleWhere((extType) => extType.name == 'JSObject')
+          // Nullability is ignored in this lint, so just return `thisType`.
+          .thisType
+      : null;
 }
 
 /// Erases extension types except for JS interop types so that
@@ -138,8 +134,6 @@
 
   final _visitedTypes = <DartType>{};
 
-  EraseNonJSInteropTypes();
-
   @override
   DartType perform(DartType type, {bool keepUserInteropTypes = false}) {
     _keepUserInteropTypes = keepUserInteropTypes;
@@ -150,13 +144,13 @@
   @override
   DartType? visitInterfaceType(covariant InterfaceTypeImpl type) {
     if (_keepUserInteropTypes
-        ? isUserJsInteropType(type)
-        : isDartJsInteropType(type)) {
+        ? _isUserJsInteropType(type)
+        : _isDartJsInteropType(type)) {
       // Nullability and generics on interop types are ignored for this lint. In
       // order to just compare the interfaces themselves, we use `thisType`.
       return type.element.thisType;
     } else {
-      return getJsTypeForStaticInterop(type) ?? super.visitInterfaceType(type);
+      return _jsTypeForStaticInterop(type) ?? super.visitInterfaceType(type);
     }
   }
 
@@ -200,7 +194,7 @@
   @override
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
-    var visitor = _Visitor(this, context.typeSystem, context.typeProvider);
+    var visitor = _Visitor(this, context.typeSystem);
     registry.addIsExpression(this, visitor);
     registry.addAsExpression(this, visitor);
   }
@@ -209,10 +203,9 @@
 class _Visitor extends SimpleAstVisitor<void> {
   final LintRule rule;
   final TypeSystemImpl typeSystem;
-  final TypeProvider typeProvider;
-  late final EraseNonJSInteropTypes _eraseNonJsInteropTypes;
+  final EraseNonJSInteropTypes _eraseNonJsInteropTypes;
 
-  _Visitor(this.rule, TypeSystem typeSystem, this.typeProvider)
+  _Visitor(this.rule, TypeSystem typeSystem)
       : typeSystem = typeSystem as TypeSystemImpl,
         _eraseNonJsInteropTypes = EraseNonJSInteropTypes();
 
@@ -254,73 +247,71 @@
   /// Types that belong to JS interop libraries that are not available when
   /// compiling to Wasm are ignored. Nullability is also ignored for the purpose
   /// of this test.
-  LintCode? getInvalidJsInteropTypeTest(DartType? leftType, DartType? rightType,
+  LintCode? getInvalidJsInteropTypeTest(DartType leftType, DartType rightType,
       {required bool check}) {
-    if (leftType == null || rightType == null) return null;
     LintCode? lintCode;
     (DartType, DartType) eraseTypes(DartType left, DartType right) {
       var erasedLeft =
           typeSystem.promoteToNonNull(_eraseNonJsInteropTypes.perform(left));
       var erasedRight =
           typeSystem.promoteToNonNull(_eraseNonJsInteropTypes.perform(right));
-      var leftIsInteropType = isDartJsInteropType(erasedLeft);
-      var rightIsInteropType = isDartJsInteropType(erasedRight);
+      var leftIsInteropType = _isDartJsInteropType(erasedLeft);
+      var rightIsInteropType = _isDartJsInteropType(erasedRight);
       // If there's already an invalid check in this `canBeSubtypeOf` check, we
       // are already going to lint, so only continue checking if we haven't
       // found an issue.
-      if (lintCode == null) {
-        if (leftIsInteropType || rightIsInteropType) {
-          if (!isWasmIncompatibleJsInterop(erasedLeft) &&
-              !isWasmIncompatibleJsInterop(erasedRight)) {
-            var erasedLeftIsSubtype =
-                typeSystem.isSubtypeOf(erasedLeft, erasedRight);
-            var erasedRightIsSubtype =
-                typeSystem.isSubtypeOf(erasedRight, erasedLeft);
-            var erasedLeftIsDynamic = erasedLeft is DynamicType;
-            var erasedRightIsDynamic = erasedRight is DynamicType;
-            if (check) {
-              if (!erasedLeftIsSubtype && !erasedRightIsDynamic) {
-                if (leftIsInteropType && rightIsInteropType) {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_js_is_inconsistent_js;
-                } else if (leftIsInteropType) {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_js_is_dart;
-                } else {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_dart_is_js;
-                }
-              } else if (erasedLeftIsSubtype &&
-                  leftIsInteropType &&
-                  rightIsInteropType) {
-                // Only report if the right type is a user JS interop type.
-                // Checks like `window is JSAny` are not confusing and not worth
-                // linting.
-                if (isUserJsInteropType(right) &&
-                    !typeSystem.isSubtypeOf(
-                        _eraseNonJsInteropTypes.perform(left,
-                            keepUserInteropTypes: true),
-                        _eraseNonJsInteropTypes.perform(right,
-                            keepUserInteropTypes: true))) {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_js_is_unrelated_js;
-                }
+
+      if (lintCode == null && leftIsInteropType || rightIsInteropType) {
+        if (!_isWasmIncompatibleJsInterop(erasedLeft) &&
+            !_isWasmIncompatibleJsInterop(erasedRight)) {
+          var erasedLeftIsSubtype =
+              typeSystem.isSubtypeOf(erasedLeft, erasedRight);
+          var erasedRightIsSubtype =
+              typeSystem.isSubtypeOf(erasedRight, erasedLeft);
+          var erasedLeftIsDynamic = erasedLeft is DynamicType;
+          var erasedRightIsDynamic = erasedRight is DynamicType;
+          if (check) {
+            if (!erasedLeftIsSubtype && !erasedRightIsDynamic) {
+              if (leftIsInteropType && rightIsInteropType) {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_js_is_inconsistent_js;
+              } else if (leftIsInteropType) {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_js_is_dart;
+              } else {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_dart_is_js;
               }
-            } else {
-              if (!erasedLeftIsSubtype &&
-                  !erasedRightIsSubtype &&
-                  !erasedLeftIsDynamic &&
-                  !erasedRightIsDynamic) {
-                if (leftIsInteropType && rightIsInteropType) {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_js_as_incompatible_js;
-                } else if (leftIsInteropType) {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_js_as_dart;
-                } else {
-                  lintCode = LinterLintCode
-                      .invalid_runtime_check_with_js_interop_types_dart_as_js;
-                }
+            } else if (erasedLeftIsSubtype &&
+                leftIsInteropType &&
+                rightIsInteropType) {
+              // Only report if the right type is a user JS interop type.
+              // Checks like `window is JSAny` are not confusing and not worth
+              // linting.
+              if (_isUserJsInteropType(right) &&
+                  !typeSystem.isSubtypeOf(
+                      _eraseNonJsInteropTypes.perform(left,
+                          keepUserInteropTypes: true),
+                      _eraseNonJsInteropTypes.perform(right,
+                          keepUserInteropTypes: true))) {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_js_is_unrelated_js;
+              }
+            }
+          } else {
+            if (!erasedLeftIsSubtype &&
+                !erasedRightIsSubtype &&
+                !erasedLeftIsDynamic &&
+                !erasedRightIsDynamic) {
+              if (leftIsInteropType && rightIsInteropType) {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_js_as_incompatible_js;
+              } else if (leftIsInteropType) {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_js_as_dart;
+              } else {
+                lintCode = LinterLintCode
+                    .invalid_runtime_check_with_js_interop_types_dart_as_js;
               }
             }
           }
@@ -334,6 +325,7 @@
       return (erasedLeft, erasedRight);
     }
 
+    // Called here for the side effects of `eraseTypes`.
     typeSystem.canBeSubtypeOf(leftType, rightType, eraseTypes: eraseTypes);
     return lintCode;
   }
@@ -342,14 +334,10 @@
   void visitAsExpression(AsExpression node) {
     var leftType = node.expression.staticType;
     var rightType = node.type.type;
+    if (leftType == null || rightType == null) return;
     var code = getInvalidJsInteropTypeTest(leftType, rightType, check: false);
     if (code != null) {
-      rule.reportLint(node,
-          arguments: [
-            leftType!.getDisplayString(),
-            rightType!.getDisplayString(),
-          ],
-          errorCode: code);
+      rule.reportLint(node, arguments: [leftType, rightType], errorCode: code);
     }
   }
 
@@ -357,14 +345,17 @@
   void visitIsExpression(IsExpression node) {
     var leftType = node.expression.staticType;
     var rightType = node.type.type;
+    if (leftType == null || rightType == null) return;
     var code = getInvalidJsInteropTypeTest(leftType, rightType, check: true);
     if (code != null) {
-      rule.reportLint(node,
-          arguments: [
-            leftType!.getDisplayString(),
-            rightType!.getDisplayString(),
-          ],
-          errorCode: code);
+      rule.reportLint(node, arguments: [leftType, rightType], errorCode: code);
     }
   }
 }
+
+extension on Element {
+  /// Returns whether this is from the Dart library at [uri].
+  bool isFromLibrary(String uri) =>
+      library?.definingCompilationUnit.source ==
+      context.sourceFactory.forUri(uri);
+}