[pkg:js] Lower external static @staticInterop class members

Lowers external static class members (fields, getters, setters, methods)
on a @staticInterop class to their respective js_util transformations.
Tests are added for a non-namespaced and a namespaced library.

Change-Id: Ife0b53c05310e924ebfbcf2059f6048ae8447c2f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/277047
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
index bea8ac4..f5b6b44 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
@@ -7,7 +7,8 @@
 import 'package:kernel/core_types.dart';
 import 'package:kernel/type_environment.dart';
 
-import '../js_interop.dart' show getJSName, hasTrustTypesAnnotation;
+import '../js_interop.dart'
+    show getJSName, hasStaticInteropAnnotation, hasTrustTypesAnnotation;
 
 /// Replaces js_util methods with inline calls to foreign_helper JS which
 /// emits the code as a JavaScript code fragment.
@@ -20,6 +21,8 @@
   final List<Procedure> _callConstructorUncheckedTargets;
   final Procedure _getPropertyTarget;
   final Procedure _getPropertyTrustTypeTarget;
+  final Procedure _globalThisTarget;
+  final InterfaceType _objectType;
   final Procedure _setPropertyTarget;
   final Procedure _setPropertyUncheckedTarget;
 
@@ -65,6 +68,9 @@
             .getTopLevelProcedure('dart:js_util', 'getProperty'),
         _getPropertyTrustTypeTarget = _coreTypes.index
             .getTopLevelProcedure('dart:js_util', '_getPropertyTrustType'),
+        _globalThisTarget = _coreTypes.index
+            .getTopLevelProcedure('dart:js_util', 'get:globalThis'),
+        _objectType = hierarchy.coreTypes.objectNonNullableRawType,
         _setPropertyTarget = _coreTypes.index
             .getTopLevelProcedure('dart:js_util', 'setProperty'),
         _setPropertyUncheckedTarget = _coreTypes.index
@@ -102,24 +108,53 @@
   visitProcedure(Procedure node) {
     _staticTypeContext.enterMember(node);
     ReturnStatement? transformedBody;
-    if (node.isExternal && node.isExtensionMember) {
-      var index = _extensionMemberIndex ??=
-          _createExtensionMembersIndex(node.enclosingLibrary);
-      Reference reference = node.reference;
-      var nodeDescriptor = index[reference]!;
-      bool shouldTrustType = _shouldTrustType.contains(reference);
-      if (!nodeDescriptor.isStatic) {
-        if (nodeDescriptor.kind == ExtensionMemberKind.Getter) {
-          transformedBody = _getExternalGetterBody(node, shouldTrustType);
-        } else if (nodeDescriptor.kind == ExtensionMemberKind.Setter) {
-          transformedBody = _getExternalSetterBody(node);
-        } else if (nodeDescriptor.kind == ExtensionMemberKind.Method) {
-          transformedBody = _getExternalMethodBody(node, shouldTrustType);
+    if (node.isExternal) {
+      if (node.isExtensionMember) {
+        var index = _extensionMemberIndex ??=
+            _createExtensionMembersIndex(node.enclosingLibrary);
+        Reference reference = node.reference;
+        var nodeDescriptor = index[reference]!;
+        bool shouldTrustType = _shouldTrustType.contains(reference);
+        if (!nodeDescriptor.isStatic) {
+          if (nodeDescriptor.kind == ExtensionMemberKind.Getter) {
+            transformedBody = _getExternalGetterBody(node, shouldTrustType);
+          } else if (nodeDescriptor.kind == ExtensionMemberKind.Setter) {
+            transformedBody = _getExternalSetterBody(node);
+          } else if (nodeDescriptor.kind == ExtensionMemberKind.Method) {
+            transformedBody = _getExternalMethodBody(node, shouldTrustType);
+          }
+        }
+      } else if (node.isStatic) {
+        // Fetch the dotted prefix of the member.
+        var libraryName = getJSName(node.enclosingLibrary);
+        var dottedPrefix = libraryName;
+        var enclosingClass = node.enclosingClass;
+        if (enclosingClass != null &&
+            hasStaticInteropAnnotation(enclosingClass)) {
+          var className = getJSName(enclosingClass);
+          if (className.isEmpty) className = enclosingClass.name;
+          if (dottedPrefix.isNotEmpty) {
+            dottedPrefix = '$dottedPrefix.$className';
+          } else {
+            dottedPrefix = className;
+          }
+          var shouldTrustType = hasTrustTypesAnnotation(enclosingClass);
+          var receiver = _getObjectOffGlobalThis(node, dottedPrefix.split('.'));
+          if (node.kind == ProcedureKind.Getter) {
+            transformedBody =
+                _getExternalGetterBody(node, shouldTrustType, receiver);
+          } else if (node.kind == ProcedureKind.Setter) {
+            transformedBody = _getExternalSetterBody(node, receiver);
+          } else if (node.kind == ProcedureKind.Method) {
+            transformedBody =
+                _getExternalMethodBody(node, shouldTrustType, receiver);
+          }
         }
       }
     }
     if (transformedBody != null) {
       node.function.body = transformedBody;
+      transformedBody.parent = node.function;
       node.isExternal = false;
     } else {
       node.transformChildren(this);
@@ -128,6 +163,23 @@
     return node;
   }
 
+  /// Given a list of strings, [selectors], recursively fetches the property
+  /// that corresponds to each string off of the `globalThis` object.
+  ///
+  /// Returns an expression that contains the nested property gets.
+  Expression _getObjectOffGlobalThis(Procedure node, List<String> selectors) {
+    Expression currentTarget = StaticGet(_globalThisTarget)
+      ..fileOffset = node.fileOffset;
+    for (String selector in selectors) {
+      currentTarget = StaticInvocation(
+          _getPropertyTrustTypeTarget,
+          Arguments([currentTarget, StringLiteral(selector)],
+              types: [_objectType]))
+        ..fileOffset = node.fileOffset;
+    }
+    return currentTarget;
+  }
+
   /// Returns and initializes `_extensionMemberIndex` to an index of the member
   /// reference to the member `ExtensionMemberDescriptor`, for all extension
   /// members in the given [library].
@@ -151,17 +203,23 @@
   /// Returns a new function body for the given [node] external getter.
   ///
   /// The new function body will call the optimized version of
-  /// `js_util.getProperty` for the given external getter.
-  ReturnStatement _getExternalGetterBody(Procedure node, bool shouldTrustType) {
+  /// `js_util.getProperty` for the given external getter. If [shouldTrustType]
+  /// is true, we call a variant that does not check the return type. If
+  /// [receiver] is non-null, we use that instead of the first positional
+  /// parameter as the receiver for `js_util.getProperty`.
+  ReturnStatement _getExternalGetterBody(Procedure node, bool shouldTrustType,
+      [Expression? receiver]) {
     var function = node.function;
-    assert(function.positionalParameters.length == 1);
+    // Parameter `this` only exists for instance extension members.
+    assert(function.positionalParameters.length ==
+        (_isInstanceExtensionMember(node) ? 1 : 0));
     Procedure target =
         shouldTrustType ? _getPropertyTrustTypeTarget : _getPropertyTarget;
     var getPropertyInvocation = StaticInvocation(
         target,
         Arguments([
-          VariableGet(function.positionalParameters.first),
-          StringLiteral(_getExtensionMemberName(node))
+          receiver ?? VariableGet(function.positionalParameters.first),
+          StringLiteral(_getMemberJSName(node))
         ], types: [
           function.returnType
         ]))
@@ -172,16 +230,21 @@
   /// Returns a new function body for the given [node] external setter.
   ///
   /// The new function body will call the optimized version of
-  /// `js_util.setProperty` for the given external setter.
-  ReturnStatement _getExternalSetterBody(Procedure node) {
+  /// `js_util.setProperty` for the given external setter. If [receiver] is
+  /// non-null, we use that instead of the first positional parameter as the
+  /// receiver for `js_util.setProperty`.
+  ReturnStatement _getExternalSetterBody(Procedure node,
+      [Expression? receiver]) {
     var function = node.function;
-    assert(function.positionalParameters.length == 2);
+    // Parameter `this` only exists for instance extension members.
+    assert(function.positionalParameters.length ==
+        (_isInstanceExtensionMember(node) ? 2 : 1));
     var value = function.positionalParameters.last;
     var setPropertyInvocation = StaticInvocation(
         _setPropertyTarget,
         Arguments([
-          VariableGet(function.positionalParameters.first),
-          StringLiteral(_getExtensionMemberName(node)),
+          receiver ?? VariableGet(function.positionalParameters.first),
+          StringLiteral(_getMemberJSName(node)),
           VariableGet(value)
         ], types: [
           value.type
@@ -193,18 +256,26 @@
   /// Returns a new function body for the given [node] external method.
   ///
   /// The new function body will call the optimized version of
-  /// `js_util.callMethod` for the given external method.
-  ReturnStatement _getExternalMethodBody(Procedure node, bool shouldTrustType) {
+  /// `js_util.callMethod` for the given external method. If [shouldTrustType]
+  /// is true, we call a variant that does not check the return type. If
+  /// [receiver] is non-null, we use that instead of the first positional
+  /// parameter as the receiver for `js_util.callMethod`.
+  ReturnStatement _getExternalMethodBody(Procedure node, bool shouldTrustType,
+      [Expression? receiver]) {
     var function = node.function;
     Procedure target =
         shouldTrustType ? _callMethodTrustTypeTarget : _callMethodTarget;
+    var positionalParameters = function.positionalParameters;
+    if (_isInstanceExtensionMember(node)) {
+      // Ignore `this` for instance extension members.
+      positionalParameters = positionalParameters.sublist(1);
+    }
     var callMethodInvocation = StaticInvocation(
         target,
         Arguments([
-          VariableGet(function.positionalParameters.first),
-          StringLiteral(_getExtensionMemberName(node)),
-          ListLiteral(function.positionalParameters
-              .sublist(1)
+          receiver ?? VariableGet(function.positionalParameters.first),
+          StringLiteral(_getMemberJSName(node)),
+          ListLiteral(positionalParameters
               .map<Expression>((argument) => VariableGet(argument))
               .toList())
         ], types: [
@@ -215,17 +286,26 @@
         shouldTrustType: shouldTrustType));
   }
 
-  /// Returns the extension member name.
+  /// Return whether [node] is an extension member that's declared as
+  /// non-`static`.
+  bool _isInstanceExtensionMember(Member node) =>
+      node.isExtensionMember &&
+      !_extensionMemberIndex![node.reference]!.isStatic;
+
+  /// Returns the underlying JS name.
   ///
   /// Returns either the name from the `@JS` annotation if non-empty, or the
-  /// declared name of the extension member. Does not return the CFE generated
-  /// name for the top level member for this extension member.
-  String _getExtensionMemberName(Procedure node) {
+  /// declared name of the member. In the case of an extension member, this
+  /// does not return the CFE generated name for the top level member.
+  String _getMemberJSName(Procedure node) {
     var jsAnnotationName = getJSName(node);
     if (jsAnnotationName.isNotEmpty) {
       return jsAnnotationName;
     }
-    return _extensionMemberIndex![node.reference]!.name.text;
+    if (node.isExtensionMember) {
+      return _extensionMemberIndex![node.reference]!.name.text;
+    }
+    return node.name.text;
   }
 
   /// Replaces js_util method calls with optimization when possible.
@@ -286,12 +366,13 @@
         node, targets, arguments.positional.sublist(0, 2));
   }
 
-  /// Lowers the given js_util `callConstructor` call to `_callConstructorUncheckedN`
-  /// when the additional validation checks on the arguments can be elided.
+  /// Lowers the given js_util `callConstructor` call to
+  /// `_callConstructorUncheckedN` when the additional validation checks on the
+  /// arguments can be elided.
   ///
   /// Calls will be lowered when using a List literal or constant list with 0-4
-  /// elements for the `callConstructor` arguments, or the `List.empty()` factory.
-  /// Removing the checks allows further inlining by the compilers.
+  /// elements for the `callConstructor` arguments, or the `List.empty()`
+  /// factory. Removing the checks allows further inlining by the compilers.
   StaticInvocation _lowerCallConstructor(StaticInvocation node) {
     Arguments arguments = node.arguments;
     assert(arguments.positional.length == 2);
diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart
new file mode 100644
index 0000000..0035255
--- /dev/null
+++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@JS()
+library external_static_member_lowerings_test;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+@staticInterop
+class ExternalStatic {
+  external static String field;
+  @JS('field')
+  external static String renamedField;
+  external static final String finalField;
+
+  external static String get getSet;
+  external static set getSet(String val);
+  @JS('getSet')
+  external static String get renamedGetSet;
+  @JS('getSet')
+  external static set renamedGetSet(String val);
+
+  external static String method();
+  external static String differentArgsMethod(String a, [String b = '']);
+  @JS('method')
+  external static String renamedMethod();
+}
+
+@JS('ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+  external static double field;
+  external static double get getSet;
+  external static double method();
+}
+
+void main() {
+  eval('''
+    globalThis.ExternalStatic = function ExternalStatic() {}
+    globalThis.ExternalStatic.method = function() {
+      return 'method';
+    }
+    globalThis.ExternalStatic.differentArgsMethod = function(a, b) {
+      return a + b;
+    }
+    globalThis.ExternalStatic.field = 'field';
+    globalThis.ExternalStatic.finalField = 'finalField';
+    globalThis.ExternalStatic.getSet = 'getSet';
+  ''');
+  testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+  // Fields.
+  expect(ExternalStatic.field, 'field');
+  ExternalStatic.field = 'modified';
+  expect(ExternalStatic.field, 'modified');
+  expect(ExternalStatic.renamedField, 'modified');
+  ExternalStatic.renamedField = 'renamedField';
+  expect(ExternalStatic.renamedField, 'renamedField');
+  expect(ExternalStatic.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(ExternalStatic.getSet, 'getSet');
+  ExternalStatic.getSet = 'modified';
+  expect(ExternalStatic.getSet, 'modified');
+  expect(ExternalStatic.renamedGetSet, 'modified');
+  ExternalStatic.renamedGetSet = 'renamedGetSet';
+  expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+  // Methods and tear-offs.
+  expect(ExternalStatic.method(), 'method');
+  expect((ExternalStatic.method)(), 'method');
+  expect(ExternalStatic.differentArgsMethod('method'), 'method');
+  expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+      'optionalmethod');
+  expect(ExternalStatic.renamedMethod(), 'method');
+  expect((ExternalStatic.renamedMethod)(), 'method');
+
+  // Use wrong return type in conjunction with `@trustTypes`.
+  expect(ExternalStaticTrustType.field, 'renamedField');
+
+  expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+  expect(ExternalStaticTrustType.method(), 'method');
+  expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
new file mode 100644
index 0000000..fd6eead
--- /dev/null
+++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Like `external_static_member_lowerings_test.dart`, but uses the namespaces
+// in the `@JS` annotations instead.
+
+@JS('library1.library2')
+library external_static_member_lowerings_with_namespaces_test;
+
+import 'dart:js_util' as js_util;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS('library3.ExternalStatic')
+@staticInterop
+class ExternalStatic {
+  external static String field;
+  @JS('field')
+  external static String renamedField;
+  external static final String finalField;
+
+  external static String get getSet;
+  external static set getSet(String val);
+  @JS('getSet')
+  external static String get renamedGetSet;
+  @JS('getSet')
+  external static set renamedGetSet(String val);
+
+  external static String method();
+  external static String differentArgsMethod(String a, [String b = '']);
+  @JS('method')
+  external static String renamedMethod();
+}
+
+@JS('library3.ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+  external static double field;
+  external static double get getSet;
+  external static double method();
+}
+
+void main() {
+  // Use `callMethod` instead of top-level external to `eval` since the library
+  // is namespaced.
+  js_util.callMethod(js_util.globalThis, 'eval', [
+    '''
+    var library3 = {};
+    var library2 = {library3: library3};
+    var library1 = {library2: library2};
+    globalThis.library1 = library1;
+
+    library3.ExternalStatic = function ExternalStatic() {}
+    library3.ExternalStatic.method = function() {
+      return 'method';
+    }
+    library3.ExternalStatic.differentArgsMethod = function(a, b) {
+      return a + b;
+    }
+    library3.ExternalStatic.field = 'field';
+    library3.ExternalStatic.finalField = 'finalField';
+    library3.ExternalStatic.getSet = 'getSet';
+  '''
+  ]);
+  testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+  // Fields.
+  expect(ExternalStatic.field, 'field');
+  ExternalStatic.field = 'modified';
+  expect(ExternalStatic.field, 'modified');
+  expect(ExternalStatic.renamedField, 'modified');
+  ExternalStatic.renamedField = 'renamedField';
+  expect(ExternalStatic.renamedField, 'renamedField');
+  expect(ExternalStatic.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(ExternalStatic.getSet, 'getSet');
+  ExternalStatic.getSet = 'modified';
+  expect(ExternalStatic.getSet, 'modified');
+  expect(ExternalStatic.renamedGetSet, 'modified');
+  ExternalStatic.renamedGetSet = 'renamedGetSet';
+  expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+  // Methods and tearoffs.
+  expect(ExternalStatic.method(), 'method');
+  expect((ExternalStatic.method)(), 'method');
+  expect(ExternalStatic.differentArgsMethod('method'), 'method');
+  expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+      'optionalmethod');
+  expect(ExternalStatic.renamedMethod(), 'method');
+  expect((ExternalStatic.renamedMethod)(), 'method');
+
+  // Use wrong return type in conjunction with `@trustTypes`.
+  expect(ExternalStaticTrustType.field, 'renamedField');
+
+  expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+  expect(ExternalStaticTrustType.method(), 'method');
+  expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 6f41bfa..cad10e6 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -7,6 +7,10 @@
 [ $arch == simarm ]
 convert/utf85_test: Skip # Pass, Slow Issue 12644.
 
+[ $compiler == dart2wasm ]
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+
 [ $mode == product ]
 developer/timeline_test: Skip # Not supported
 isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
@@ -59,6 +63,8 @@
 js/method_call_on_object_test: SkipByDesign # Issue 42085.
 js/mock_test/*: SkipByDesign # Issue 42085.
 js/parameters_test: SkipByDesign # Issue 42085.
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 
 [ $compiler != dart2js && $compiler != dartdevc && $compiler != dartdevk ]
diff --git a/tests/lib_2/js/static_interop_test/external_static_member_lowerings_test.dart b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_test.dart
new file mode 100644
index 0000000..0035255
--- /dev/null
+++ b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@JS()
+library external_static_member_lowerings_test;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+@staticInterop
+class ExternalStatic {
+  external static String field;
+  @JS('field')
+  external static String renamedField;
+  external static final String finalField;
+
+  external static String get getSet;
+  external static set getSet(String val);
+  @JS('getSet')
+  external static String get renamedGetSet;
+  @JS('getSet')
+  external static set renamedGetSet(String val);
+
+  external static String method();
+  external static String differentArgsMethod(String a, [String b = '']);
+  @JS('method')
+  external static String renamedMethod();
+}
+
+@JS('ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+  external static double field;
+  external static double get getSet;
+  external static double method();
+}
+
+void main() {
+  eval('''
+    globalThis.ExternalStatic = function ExternalStatic() {}
+    globalThis.ExternalStatic.method = function() {
+      return 'method';
+    }
+    globalThis.ExternalStatic.differentArgsMethod = function(a, b) {
+      return a + b;
+    }
+    globalThis.ExternalStatic.field = 'field';
+    globalThis.ExternalStatic.finalField = 'finalField';
+    globalThis.ExternalStatic.getSet = 'getSet';
+  ''');
+  testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+  // Fields.
+  expect(ExternalStatic.field, 'field');
+  ExternalStatic.field = 'modified';
+  expect(ExternalStatic.field, 'modified');
+  expect(ExternalStatic.renamedField, 'modified');
+  ExternalStatic.renamedField = 'renamedField';
+  expect(ExternalStatic.renamedField, 'renamedField');
+  expect(ExternalStatic.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(ExternalStatic.getSet, 'getSet');
+  ExternalStatic.getSet = 'modified';
+  expect(ExternalStatic.getSet, 'modified');
+  expect(ExternalStatic.renamedGetSet, 'modified');
+  ExternalStatic.renamedGetSet = 'renamedGetSet';
+  expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+  // Methods and tear-offs.
+  expect(ExternalStatic.method(), 'method');
+  expect((ExternalStatic.method)(), 'method');
+  expect(ExternalStatic.differentArgsMethod('method'), 'method');
+  expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+      'optionalmethod');
+  expect(ExternalStatic.renamedMethod(), 'method');
+  expect((ExternalStatic.renamedMethod)(), 'method');
+
+  // Use wrong return type in conjunction with `@trustTypes`.
+  expect(ExternalStaticTrustType.field, 'renamedField');
+
+  expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+  expect(ExternalStaticTrustType.method(), 'method');
+  expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib_2/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
new file mode 100644
index 0000000..fd6eead
--- /dev/null
+++ b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Like `external_static_member_lowerings_test.dart`, but uses the namespaces
+// in the `@JS` annotations instead.
+
+@JS('library1.library2')
+library external_static_member_lowerings_with_namespaces_test;
+
+import 'dart:js_util' as js_util;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS('library3.ExternalStatic')
+@staticInterop
+class ExternalStatic {
+  external static String field;
+  @JS('field')
+  external static String renamedField;
+  external static final String finalField;
+
+  external static String get getSet;
+  external static set getSet(String val);
+  @JS('getSet')
+  external static String get renamedGetSet;
+  @JS('getSet')
+  external static set renamedGetSet(String val);
+
+  external static String method();
+  external static String differentArgsMethod(String a, [String b = '']);
+  @JS('method')
+  external static String renamedMethod();
+}
+
+@JS('library3.ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+  external static double field;
+  external static double get getSet;
+  external static double method();
+}
+
+void main() {
+  // Use `callMethod` instead of top-level external to `eval` since the library
+  // is namespaced.
+  js_util.callMethod(js_util.globalThis, 'eval', [
+    '''
+    var library3 = {};
+    var library2 = {library3: library3};
+    var library1 = {library2: library2};
+    globalThis.library1 = library1;
+
+    library3.ExternalStatic = function ExternalStatic() {}
+    library3.ExternalStatic.method = function() {
+      return 'method';
+    }
+    library3.ExternalStatic.differentArgsMethod = function(a, b) {
+      return a + b;
+    }
+    library3.ExternalStatic.field = 'field';
+    library3.ExternalStatic.finalField = 'finalField';
+    library3.ExternalStatic.getSet = 'getSet';
+  '''
+  ]);
+  testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+  // Fields.
+  expect(ExternalStatic.field, 'field');
+  ExternalStatic.field = 'modified';
+  expect(ExternalStatic.field, 'modified');
+  expect(ExternalStatic.renamedField, 'modified');
+  ExternalStatic.renamedField = 'renamedField';
+  expect(ExternalStatic.renamedField, 'renamedField');
+  expect(ExternalStatic.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(ExternalStatic.getSet, 'getSet');
+  ExternalStatic.getSet = 'modified';
+  expect(ExternalStatic.getSet, 'modified');
+  expect(ExternalStatic.renamedGetSet, 'modified');
+  ExternalStatic.renamedGetSet = 'renamedGetSet';
+  expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+  // Methods and tearoffs.
+  expect(ExternalStatic.method(), 'method');
+  expect((ExternalStatic.method)(), 'method');
+  expect(ExternalStatic.differentArgsMethod('method'), 'method');
+  expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+      'optionalmethod');
+  expect(ExternalStatic.renamedMethod(), 'method');
+  expect((ExternalStatic.renamedMethod)(), 'method');
+
+  // Use wrong return type in conjunction with `@trustTypes`.
+  expect(ExternalStaticTrustType.field, 'renamedField');
+
+  expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+  expect(ExternalStaticTrustType.method(), 'method');
+  expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index 2574799..b7350ac 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -7,6 +7,10 @@
 [ $arch == simarm ]
 convert/utf85_test: Skip # Pass, Slow Issue 12644.
 
+[ $compiler == dart2wasm ]
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+
 [ $mode == product ]
 developer/timeline_test: Skip # Not supported
 isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
@@ -59,6 +63,8 @@
 js/method_call_on_object_test: SkipByDesign # Issue 42085.
 js/mock_test/*: SkipByDesign # Issue 42085.
 js/parameters_test: SkipByDesign # Issue 42085.
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 
 [ $compiler != dart2js && $compiler != dartdevc && $compiler != dartdevk ]