[beta][dart2wasm] Use node's enclosing library annotation for lowerings

Closes https://github.com/dart-lang/sdk/issues/55359

The current library's annotation is used for the interop lowerings
in dart2wasm. For most members, this is okay because we emit the
equivalent JS code for the member when we visit the procedure and
not when we visit the invocation. However, for methods, the invocation
determines the resulting JS call due to the existence of optional
parameters. In that case, if the invocation was not in the same
library as the interop member declaration, it results in using the
wrong library's annotation value.

Adds tests for this case and does some cleanup of existing tests.

Specifically:
- Adds a consistent naming scheme for test libraries that are
namespaced.
- Adds code to delete the non-namespaced declarations so that the
namespaced interop methods don't accidentally call those declarations.
- Removes differentArgsMethod which was already tested in
js_default_test.
- Removes use of js_util in favor of js_interop_unsafe.

Cherry-pick: https://dart-review.googlesource.com/c/sdk/+/361241
Cherry-pick-request: https://github.com/dart-lang/sdk/issues/55430
Bug: https://github.com/dart-lang/sdk/issues/55359
Change-Id: Ibf7125fea6da7722549f3c87aafdf881132b104f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362195
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Kevin Chisholm <kevinjchisholm@google.com>
diff --git a/pkg/dart2wasm/lib/js/interop_specializer.dart b/pkg/dart2wasm/lib/js/interop_specializer.dart
index 7acec36..f4250d2 100644
--- a/pkg/dart2wasm/lib/js/interop_specializer.dart
+++ b/pkg/dart2wasm/lib/js/interop_specializer.dart
@@ -341,19 +341,11 @@
   final MethodCollector _methodCollector;
   final Map<Procedure, Map<int, Procedure>> _overloadedProcedures = {};
   final Map<Procedure, Map<String, Procedure>> _jsObjectLiteralMethods = {};
-  late String _libraryJSString;
   late final ExtensionIndex _extensionIndex;
 
   InteropSpecializerFactory(this._staticTypeContext, this._util,
       this._methodCollector, this._extensionIndex);
 
-  void enterLibrary(Library library) {
-    _libraryJSString = getJSName(library);
-    if (_libraryJSString.isNotEmpty) {
-      _libraryJSString = '$_libraryJSString.';
-    }
-  }
-
   String _getJSString(Annotatable a, String initial) {
     String selectorString = getJSName(a);
     if (selectorString.isEmpty) {
@@ -362,8 +354,13 @@
     return selectorString;
   }
 
-  String _getTopLevelJSString(Annotatable a, String initial) =>
-      '$_libraryJSString${_getJSString(a, initial)}';
+  String _getTopLevelJSString(
+      Annotatable a, String writtenName, Library enclosingLibrary) {
+    final name = _getJSString(a, writtenName);
+    final libraryName = getJSName(enclosingLibrary);
+    if (libraryName.isEmpty) return name;
+    return '$libraryName.$name';
+  }
 
   /// Get the `_Specializer` for the non-constructor [node] with its
   /// associated [jsString] name, and the [invocation] it's used in if this is
@@ -420,7 +417,8 @@
     if (node.enclosingClass != null &&
         hasJSInteropAnnotation(node.enclosingClass!)) {
       final cls = node.enclosingClass!;
-      final clsString = _getTopLevelJSString(cls, cls.name);
+      final clsString =
+          _getTopLevelJSString(cls, cls.name, cls.enclosingLibrary);
       if (node.isFactory) {
         return _getSpecializerForConstructor(
             hasAnonymousAnnotation(cls), node, clsString, invocation);
@@ -433,7 +431,8 @@
       final nodeDescriptor = _extensionIndex.getExtensionTypeDescriptor(node);
       if (nodeDescriptor != null) {
         final cls = _extensionIndex.getExtensionType(node)!;
-        final clsString = _getTopLevelJSString(cls, cls.name);
+        final clsString =
+            _getTopLevelJSString(cls, cls.name, node.enclosingLibrary);
         final kind = nodeDescriptor.kind;
         if ((kind == ExtensionTypeMemberKind.Constructor ||
             kind == ExtensionTypeMemberKind.Factory)) {
@@ -462,7 +461,9 @@
       }
     } else if (hasJSInteropAnnotation(node)) {
       return _getSpecializerForMember(
-          node, _getTopLevelJSString(node, node.name.text), invocation);
+          node,
+          _getTopLevelJSString(node, node.name.text, node.enclosingLibrary),
+          invocation);
     }
     return null;
   }
diff --git a/pkg/dart2wasm/lib/js/interop_transformer.dart b/pkg/dart2wasm/lib/js/interop_transformer.dart
index c2d3210..ff2b1d4 100644
--- a/pkg/dart2wasm/lib/js/interop_transformer.dart
+++ b/pkg/dart2wasm/lib/js/interop_transformer.dart
@@ -56,7 +56,6 @@
 
   @override
   Library visitLibrary(Library lib) {
-    _interopSpecializerFactory.enterLibrary(lib);
     _methodCollector.enterLibrary(lib);
     _staticTypeContext.enterLibrary(lib);
     lib.transformChildren(this);
diff --git a/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart b/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart
index 6e53886..e20e9e8 100644
--- a/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart
+++ b/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart
@@ -2,23 +2,28 @@
 // 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.
 
+// TODO(srujzs): There's a decent amount of code duplication in this test. We
+// should combine this with
+// tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart.
+
 @JS()
 library external_static_member_test;
 
 import 'dart:js_interop';
-import 'dart:js_util' as js_util;
+import 'dart:js_interop_unsafe';
 
 import 'package:expect/minitest.dart';
 
+import 'external_static_member_with_namespaces.dart' as namespace;
+
 @JS()
 external void eval(String code);
 
 @JS()
-extension type ExternalStatic._(JSObject obj) implements Object {
+extension type ExternalStatic._(JSObject obj) implements JSObject {
   external ExternalStatic();
   external factory ExternalStatic.factory();
   external ExternalStatic.multipleArgs(double a, String b);
-  external ExternalStatic.differentArgs(double a, [String b = '']);
   ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject;
 
   external static String field;
@@ -34,39 +39,20 @@
   external static set renamedGetSet(String val);
 
   external static String method();
-  external static String differentArgsMethod(String a, [String b = '']);
   @JS('method')
   external static String renamedMethod();
 }
 
-void main() {
-  eval('''
-    globalThis.ExternalStatic = function ExternalStatic(a, b) {
-      var len = arguments.length;
-      this.a = len < 1 ? 0 : a;
-      this.b = len < 2 ? '' : b;
-    }
-    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';
-  ''');
-
+void testStaticMembers() {
   // Constructors.
   void testExternalConstructorCall(ExternalStatic externalStatic) {
-    expect(js_util.getProperty(externalStatic, 'a'), 0);
-    expect(js_util.getProperty(externalStatic, 'b'), '');
+    expect((externalStatic['a'] as JSNumber).toDartInt, 0);
+    expect((externalStatic['b'] as JSString).toDart, '');
   }
 
   testExternalConstructorCall(ExternalStatic());
   testExternalConstructorCall(ExternalStatic.factory());
   testExternalConstructorCall(ExternalStatic.multipleArgs(0, ''));
-  testExternalConstructorCall(ExternalStatic.differentArgs(0));
   testExternalConstructorCall(ExternalStatic.nonExternal());
 
   // Fields.
@@ -88,6 +74,69 @@
 
   // Methods.
   expect(ExternalStatic.method(), 'method');
-  expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
   expect(ExternalStatic.renamedMethod(), 'method');
 }
+
+void testNamespacedStaticMembers() {
+  // Constructors.
+  void testExternalConstructorCall(namespace.ExternalStatic externalStatic) {
+    expect((externalStatic['a'] as JSNumber).toDartInt, 0);
+    expect((externalStatic['b'] as JSString).toDart, '');
+  }
+
+  testExternalConstructorCall(namespace.ExternalStatic());
+  testExternalConstructorCall(namespace.ExternalStatic.factory());
+  testExternalConstructorCall(namespace.ExternalStatic.multipleArgs(0, ''));
+  testExternalConstructorCall(namespace.ExternalStatic.nonExternal());
+
+  // Fields.
+  expect(namespace.ExternalStatic.field, 'field');
+  namespace.ExternalStatic.field = 'modified';
+  expect(namespace.ExternalStatic.field, 'modified');
+  expect(namespace.ExternalStatic.renamedField, 'modified');
+  namespace.ExternalStatic.renamedField = 'renamedField';
+  expect(namespace.ExternalStatic.renamedField, 'renamedField');
+  expect(namespace.ExternalStatic.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(namespace.ExternalStatic.getSet, 'getSet');
+  namespace.ExternalStatic.getSet = 'modified';
+  expect(namespace.ExternalStatic.getSet, 'modified');
+  expect(namespace.ExternalStatic.renamedGetSet, 'modified');
+  namespace.ExternalStatic.renamedGetSet = 'renamedGetSet';
+  expect(namespace.ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+  // Methods.
+  expect(namespace.ExternalStatic.method(), 'method');
+  expect(namespace.ExternalStatic.renamedMethod(), 'method');
+}
+
+void main() {
+  eval('''
+    globalThis.ExternalStatic = function ExternalStatic(a, b) {
+      var len = arguments.length;
+      this.a = len < 1 ? 0 : a;
+      this.b = len < 2 ? '' : b;
+    }
+    globalThis.ExternalStatic.method = function() {
+      return 'method';
+    }
+    globalThis.ExternalStatic.field = 'field';
+    globalThis.ExternalStatic.finalField = 'finalField';
+    globalThis.ExternalStatic.getSet = 'getSet';
+  ''');
+  testStaticMembers();
+  eval('''
+    var library3 = {};
+    var library2 = {library3: library3};
+    var library1 = {library2: library2};
+    globalThis.library1 = library1;
+
+    library3.ExternalStatic = globalThis.ExternalStatic;
+    library3.ExternalStatic.field = 'field';
+    library3.ExternalStatic.finalField = 'finalField';
+    library3.ExternalStatic.getSet = 'getSet';
+    delete globalThis.ExternalStatic;
+  ''');
+  testNamespacedStaticMembers();
+}
diff --git a/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart b/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart
new file mode 100644
index 0000000..42ea051
--- /dev/null
+++ b/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2024, 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('library1.library2')
+library external_static_member_with_namespaces_test;
+
+import 'dart:js_interop';
+
+@JS()
+external void eval(String code);
+
+@JS('library3.ExternalStatic')
+extension type ExternalStatic._(JSObject obj) implements JSObject {
+  external ExternalStatic();
+  external factory ExternalStatic.factory();
+  external ExternalStatic.multipleArgs(double a, String b);
+  ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject;
+
+  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();
+  @JS('method')
+  external static String renamedMethod();
+}
diff --git a/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces_test.dart b/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces_test.dart
deleted file mode 100644
index d7d02fd..0000000
--- a/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces_test.dart
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2023, 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('library1.library2')
-library external_static_member_with_namespaces_test;
-
-import 'dart:js_interop';
-import 'dart:js_util' as js_util;
-
-import 'package:expect/minitest.dart';
-
-@JS()
-external void eval(String code);
-
-@JS('library3.ExternalStatic')
-extension type ExternalStatic._(JSObject obj) implements Object {
-  external ExternalStatic();
-  external factory ExternalStatic.factory();
-  external ExternalStatic.multipleArgs(double a, String b);
-  external ExternalStatic.differentArgs(double a, [String b = '']);
-  ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject;
-
-  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();
-}
-
-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(a, b) {
-      var len = arguments.length;
-      this.a = len < 1 ? 0 : a;
-      this.b = len < 2 ? '' : b;
-    }
-    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';
-  '''
-  ]);
-
-  // Constructors.
-  void testExternalConstructorCall(ExternalStatic externalStatic) {
-    expect(js_util.getProperty(externalStatic, 'a'), 0);
-    expect(js_util.getProperty(externalStatic, 'b'), '');
-  }
-
-  testExternalConstructorCall(ExternalStatic());
-  testExternalConstructorCall(ExternalStatic.factory());
-  testExternalConstructorCall(ExternalStatic.multipleArgs(0, ''));
-  testExternalConstructorCall(ExternalStatic.differentArgs(0));
-  testExternalConstructorCall(ExternalStatic.nonExternal());
-
-  // 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.
-  expect(ExternalStatic.method(), 'method');
-  expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
-  expect(ExternalStatic.renamedMethod(), 'method');
-}
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
index 730d93f..a139468 100644
--- 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
@@ -9,6 +9,8 @@
 
 import 'package:expect/minitest.dart';
 
+import 'external_static_member_lowerings_with_namespaces.dart' as namespace;
+
 @JS()
 external void eval(String code);
 
@@ -33,7 +35,6 @@
   external static set renamedGetSet(String val);
 
   external static String method();
-  external static String differentArgsMethod(String a, [String b = '']);
   @JS('method')
   external static String renamedMethod();
 }
@@ -63,41 +64,9 @@
 // Top-level methods.
 @JS()
 external String method();
-@JS()
-external String differentArgsMethod(String a, [String b = '']);
 @JS('method')
 external String renamedMethod();
 
-void main() {
-  eval('''
-    globalThis.ExternalStatic = function ExternalStatic(initialValue) {
-      this.initialValue = initialValue;
-    }
-    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';
-
-    globalThis.field = 'field';
-    globalThis.finalField = 'finalField';
-    globalThis.getSet = 'getSet';
-    globalThis.method = function() {
-      return 'method';
-    }
-    globalThis.differentArgsMethod = function(a, b) {
-      return a + b;
-    }
-  ''');
-  testClassStaticMembers();
-  testTopLevelMembers();
-  testFactories();
-}
-
 void testClassStaticMembers() {
   // Fields.
   expect(ExternalStatic.field, 'field');
@@ -118,7 +87,6 @@
 
   // Methods.
   expect(ExternalStatic.method(), 'method');
-  expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
   expect(ExternalStatic.renamedMethod(), 'method');
 }
 
@@ -142,7 +110,6 @@
 
   // Methods.
   expect(method(), 'method');
-  expect(differentArgsMethod('method'), 'methodundefined');
   expect(renamedMethod(), 'method');
 }
 
@@ -155,3 +122,109 @@
   externalStatic = ExternalStatic.named();
   expect(externalStatic.initialValue, null);
 }
+
+void testNamespacedClassStaticMembers() {
+  // Fields.
+  expect(namespace.ExternalStatic.field, 'field');
+  namespace.ExternalStatic.field = 'modified';
+  expect(namespace.ExternalStatic.field, 'modified');
+  expect(namespace.ExternalStatic.renamedField, 'modified');
+  namespace.ExternalStatic.renamedField = 'renamedField';
+  expect(namespace.ExternalStatic.renamedField, 'renamedField');
+  expect(namespace.ExternalStatic.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(namespace.ExternalStatic.getSet, 'getSet');
+  namespace.ExternalStatic.getSet = 'modified';
+  expect(namespace.ExternalStatic.getSet, 'modified');
+  expect(namespace.ExternalStatic.renamedGetSet, 'modified');
+  namespace.ExternalStatic.renamedGetSet = 'renamedGetSet';
+  expect(namespace.ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+  // Methods.
+  expect(namespace.ExternalStatic.method(), 'method');
+  expect(namespace.ExternalStatic.renamedMethod(), 'method');
+}
+
+void testNamespacedTopLevelMembers() {
+  // Fields.
+  expect(namespace.field, 'field');
+  namespace.field = 'modified';
+  expect(namespace.field, 'modified');
+  expect(namespace.renamedField, 'modified');
+  namespace.renamedField = 'renamedField';
+  expect(namespace.renamedField, 'renamedField');
+  expect(namespace.finalField, 'finalField');
+
+  // Getters and setters.
+  expect(namespace.getSet, 'getSet');
+  namespace.getSet = 'modified';
+  expect(namespace.getSet, 'modified');
+  expect(namespace.renamedGetSet, 'modified');
+  namespace.renamedGetSet = 'renamedGetSet';
+  expect(namespace.renamedGetSet, 'renamedGetSet');
+
+  // Methods.
+  expect(namespace.method(), 'method');
+  expect(namespace.renamedMethod(), 'method');
+}
+
+void testNamespacedFactories() {
+  // Non-object literal factories.
+  var initialized = 'initialized';
+
+  var externalStatic = namespace.ExternalStatic(initialized);
+  expect(externalStatic.initialValue, initialized);
+  externalStatic = namespace.ExternalStatic.named();
+  expect(externalStatic.initialValue, null);
+}
+
+void main() {
+  eval('''
+    globalThis.ExternalStatic = function ExternalStatic(initialValue) {
+      this.initialValue = initialValue;
+    }
+    globalThis.ExternalStatic.field = 'field';
+    globalThis.ExternalStatic.finalField = 'finalField';
+    globalThis.ExternalStatic.getSet = 'getSet';
+    globalThis.ExternalStatic.method = function() {
+      return 'method';
+    }
+
+    globalThis.field = 'field';
+    globalThis.finalField = 'finalField';
+    globalThis.getSet = 'getSet';
+    globalThis.method = function() {
+      return 'method';
+    }
+  ''');
+  testClassStaticMembers();
+  testTopLevelMembers();
+  testFactories();
+  // Move declarations to a namespace and delete the top-level ones to test that
+  // we use the declaration's enclosing library's annotation and not the current
+  // library's.
+  eval('''
+    var library3 = {};
+    var library2 = {library3: library3};
+    var library1 = {library2: library2};
+    globalThis.library1 = library1;
+
+    library3.ExternalStatic = globalThis.ExternalStatic;
+    library3.ExternalStatic.field = 'field';
+    library3.ExternalStatic.finalField = 'finalField';
+    library3.ExternalStatic.getSet = 'getSet';
+    delete globalThis.ExternalStatic;
+    library3.field = 'field';
+    library3.finalField = 'finalField';
+    library3.getSet = 'getSet';
+    library3.method = globalThis.method;
+    delete globalThis.field;
+    delete globalThis.finalField;
+    delete globalThis.getSet;
+    delete globalThis.method;
+  ''');
+  testNamespacedClassStaticMembers();
+  testNamespacedTopLevelMembers();
+  testNamespacedFactories();
+}
diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart
new file mode 100644
index 0000000..8c515eb
--- /dev/null
+++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2024, 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_interop';
+
+@JS('library3.ExternalStatic')
+@staticInterop
+class ExternalStatic {
+  external factory ExternalStatic(String initialValue);
+  external factory ExternalStatic.named(
+      [String initialValue = 'uninitialized']);
+  // External redirecting factories are not allowed.
+
+  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();
+  @JS('method')
+  external static String renamedMethod();
+}
+
+extension ExternalStaticExtension on ExternalStatic {
+  external String? get initialValue;
+}
+
+// Top-level fields.
+@JS('library3.field')
+external String field;
+@JS('library3.field')
+external String renamedField;
+@JS('library3.finalField')
+external final String finalField;
+
+// Top-level getters and setters.
+@JS('library3.getSet')
+external String get getSet;
+@JS('library3.getSet')
+external set getSet(String val);
+@JS('library3.getSet')
+external String get renamedGetSet;
+@JS('library3.getSet')
+external set renamedGetSet(String val);
+
+// Top-level methods.
+@JS('library3.method')
+external String method();
+@JS('library3.method')
+external String renamedMethod();
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
deleted file mode 100644
index 676220a..0000000
--- a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
+++ /dev/null
@@ -1,174 +0,0 @@
-// 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_interop';
-import 'dart:js_util' as js_util;
-
-import 'package:expect/minitest.dart';
-
-@JS('library3.ExternalStatic')
-@staticInterop
-class ExternalStatic {
-  external factory ExternalStatic(String initialValue);
-  external factory ExternalStatic.named(
-      [String initialValue = 'uninitialized']);
-  // External redirecting factories are not allowed.
-
-  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();
-}
-
-extension on ExternalStatic {
-  external String? get initialValue;
-}
-
-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(initialValue) {
-      this.initialValue = initialValue;
-    }
-    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';
-
-    library2.field = 'field';
-    library2.getSet = 'getSet';
-    library2.method = function() {
-      return 'method';
-    }
-    library2.differentArgsMethod = function(a, b) {
-      return a + b;
-    }
-    library3.namespacedField = 'namespacedField';
-    library3.namespacedGetSet = 'namespacedGetSet';
-    library3.namespacedMethod = function() {
-      return 'namespacedMethod';
-    }
-
-  '''
-  ]);
-  testClassStaticMembers();
-  testTopLevelMembers();
-  testFactories();
-}
-
-// Top-level fields.
-@JS()
-external String field;
-@JS('library3.namespacedField')
-external String namespacedField;
-@JS('field')
-external final String finalField;
-
-// Top-level getters and setters.
-@JS()
-external String get getSet;
-@JS()
-external set getSet(String val);
-@JS('library3.namespacedGetSet')
-external String get namespacedGetSet;
-@JS('library3.namespacedGetSet')
-external set namespacedGetSet(String val);
-
-// Top-level methods.
-@JS()
-external String method();
-@JS()
-external String differentArgsMethod(String a, [String b = '']);
-@JS('library3.namespacedMethod')
-external String namespacedMethod();
-
-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.
-  expect(ExternalStatic.method(), 'method');
-  expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
-  expect(ExternalStatic.renamedMethod(), 'method');
-}
-
-void testTopLevelMembers() {
-  // Test a variety of renaming and namespacing to make sure we're handling '.'
-  // correctly.
-  // Fields.
-  expect(field, 'field');
-  field = 'modified';
-  expect(field, 'modified');
-  expect(namespacedField, 'namespacedField');
-  namespacedField = 'modified';
-  expect(namespacedField, 'modified');
-  expect(finalField, 'modified');
-
-  // Getters and setters.
-  expect(getSet, 'getSet');
-  getSet = 'modified';
-  expect(getSet, 'modified');
-  expect(namespacedGetSet, 'namespacedGetSet');
-  namespacedGetSet = 'modified';
-  expect(namespacedGetSet, 'modified');
-
-  // Methods.
-  expect(method(), 'method');
-  expect(differentArgsMethod('method'), 'methodundefined');
-  expect(namespacedMethod(), 'namespacedMethod');
-}
-
-void testFactories() {
-  // Non-object literal factories.
-  var initialized = 'initialized';
-
-  var externalStatic = ExternalStatic(initialized);
-  expect(externalStatic.initialValue, initialized);
-  externalStatic = ExternalStatic.named();
-  expect(externalStatic.initialValue, null);
-}
diff --git a/tests/lib/js/static_interop_test/js_default_other_library.dart b/tests/lib/js/static_interop_test/js_default_other_library.dart
deleted file mode 100644
index fa769a3..0000000
--- a/tests/lib/js/static_interop_test/js_default_other_library.dart
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2023, 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.
-
-library js_default_other_library;
-
-import 'dart:js_interop';
-
-@JS()
-@staticInterop
-class SimpleObject {
-  external factory SimpleObject();
-  external factory SimpleObject.oneOptional(JSNumber n1, [JSNumber n2]);
-
-  external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]);
-}
-
-extension SimpleObjectExtension on SimpleObject {
-  external JSNumber get initialArguments;
-  external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
-}
-
-@JS()
-external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
diff --git a/tests/lib/js/static_interop_test/js_default_test.dart b/tests/lib/js/static_interop_test/js_default_test.dart
index 90f1c23..c87df20 100644
--- a/tests/lib/js/static_interop_test/js_default_test.dart
+++ b/tests/lib/js/static_interop_test/js_default_test.dart
@@ -12,7 +12,7 @@
 
 import 'package:expect/minitest.dart';
 
-import 'js_default_other_library.dart' as other;
+import 'js_default_with_namespaces.dart' as namespace;
 
 @JS()
 external void eval(String code);
@@ -34,6 +34,20 @@
   external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
 }
 
+@JS('SimpleObject')
+extension type SimpleObject2._(JSObject _) implements JSObject {
+  external factory SimpleObject2();
+  external factory SimpleObject2.twoOptional([JSNumber n1, JSNumber n2]);
+  external factory SimpleObject2.oneOptional(JSNumber n1, [JSNumber n2]);
+
+  external static JSNumber twoOptionalStatic([JSNumber n1, JSNumber n2]);
+  external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]);
+
+  external JSNumber get initialArguments;
+  external JSNumber twoOptional([JSNumber n1, JSNumber n2]);
+  external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
+}
+
 @JS()
 external JSNumber twoOptional([JSNumber n1, JSNumber n2]);
 
@@ -75,30 +89,77 @@
 
   expect(1, s.oneOptional(4.0.toJS).toDartInt);
   expect(2, s.oneOptional(4.0.toJS, 5.0.toJS).toDartInt);
+
+  // Test extension type factories.
+  expect(0, SimpleObject2.twoOptional().initialArguments.toDartInt);
+  expect(1, SimpleObject2.twoOptional(4.0.toJS).initialArguments.toDartInt);
+  expect(2,
+      SimpleObject2.twoOptional(4.0.toJS, 5.0.toJS).initialArguments.toDartInt);
+
+  expect(1, SimpleObject2.oneOptional(4.0.toJS).initialArguments.toDartInt);
+  expect(2,
+      SimpleObject2.oneOptional(4.0.toJS, 5.0.toJS).initialArguments.toDartInt);
+
+  // Test extension type static methods.
+  expect(0, SimpleObject2.twoOptionalStatic().toDartInt);
+  expect(1, SimpleObject2.twoOptionalStatic(4.0.toJS).toDartInt);
+  expect(2, SimpleObject2.twoOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt);
+
+  expect(1, SimpleObject2.oneOptionalStatic(4.0.toJS).toDartInt);
+  expect(2, SimpleObject2.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt);
+
+  // Test extension type methods.
+  final s2 = SimpleObject2();
+  expect(0, s2.twoOptional().toDartInt);
+  expect(1, s2.twoOptional(4.0.toJS).toDartInt);
+  expect(2, s2.twoOptional(4.0.toJS, 5.0.toJS).toDartInt);
+
+  expect(1, s2.oneOptional(4.0.toJS).toDartInt);
+  expect(2, s2.oneOptional(4.0.toJS, 5.0.toJS).toDartInt);
 }
 
 void testOtherLibrary() {
   // Test top level methods.
-  expect(1, other.oneOptional(4.0.toJS).toDartInt);
-  expect(2, other.oneOptional(4.0.toJS, 5.0.toJS).toDartInt);
+  expect(1, namespace.oneOptional(4.0.toJS).toDartInt);
+  expect(2, namespace.oneOptional(4.0.toJS, 5.0.toJS).toDartInt);
 
   // Test factories.
-  expect(
-      1, other.SimpleObject.oneOptional(4.0.toJS).initialArguments.toDartInt);
+  expect(1,
+      namespace.SimpleObject.oneOptional(4.0.toJS).initialArguments.toDartInt);
   expect(
       2,
-      other.SimpleObject.oneOptional(4.0.toJS, 5.0.toJS)
+      namespace.SimpleObject.oneOptional(4.0.toJS, 5.0.toJS)
           .initialArguments
           .toDartInt);
 
   // Test static methods.
-  expect(1, other.SimpleObject.oneOptionalStatic(4.0.toJS).toDartInt);
-  expect(2, other.SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt);
+  expect(1, namespace.SimpleObject.oneOptionalStatic(4.0.toJS).toDartInt);
+  expect(2,
+      namespace.SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt);
 
   // Test extension methods.
-  final s = other.SimpleObject();
+  final s = namespace.SimpleObject();
   expect(1, s.oneOptional(4.0.toJS).toDartInt);
   expect(2, s.oneOptional(4.0.toJS, 5.0.toJS).toDartInt);
+
+  // Test extension type factories.
+  expect(1,
+      namespace.SimpleObject.oneOptional(4.0.toJS).initialArguments.toDartInt);
+  expect(
+      2,
+      namespace.SimpleObject.oneOptional(4.0.toJS, 5.0.toJS)
+          .initialArguments
+          .toDartInt);
+
+  // Test extension type static methods.
+  expect(1, namespace.SimpleObject.oneOptionalStatic(4.0.toJS).toDartInt);
+  expect(2,
+      namespace.SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt);
+
+  // Test extension type methods.
+  final s2 = namespace.SimpleObject();
+  expect(1, s2.oneOptional(4.0.toJS).toDartInt);
+  expect(2, s2.oneOptional(4.0.toJS, 5.0.toJS).toDartInt);
 }
 
 void main() {
@@ -127,5 +188,19 @@
   }
   ''');
   testCurrentLibrary();
+  // Move the declarations to a namespace and delete the declarations on
+  // globalThis to make sure we incorporate the library prefix in
+  // invocation-level lowering.
+  eval('''
+  var library1 = {};
+  globalThis.library1 = library1;
+
+  library1.twoOptional = globalThis.twoOptional;
+  library1.oneOptional = globalThis.oneOptional;
+  delete globalThis.twoOptional;
+  delete globalThis.oneOptional;
+  library1.SimpleObject = globalThis.SimpleObject;
+  delete globalThis.SimpleObject;
+  ''');
   testOtherLibrary();
 }
diff --git a/tests/lib/js/static_interop_test/js_default_with_namespaces.dart b/tests/lib/js/static_interop_test/js_default_with_namespaces.dart
new file mode 100644
index 0000000..a42b978
--- /dev/null
+++ b/tests/lib/js/static_interop_test/js_default_with_namespaces.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2024, 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('library1')
+library js_default_with_namespaces;
+
+import 'dart:js_interop';
+
+@JS()
+@staticInterop
+class SimpleObject {
+  external factory SimpleObject();
+  external factory SimpleObject.oneOptional(JSNumber n1, [JSNumber n2]);
+
+  external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]);
+}
+
+extension SimpleObjectExtension on SimpleObject {
+  external JSNumber get initialArguments;
+  external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
+}
+
+@JS('SimpleObject')
+extension type SimpleObject2._(JSObject _) implements JSObject {
+  external factory SimpleObject2();
+  external factory SimpleObject2.oneOptional(JSNumber n1, [JSNumber n2]);
+
+  external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]);
+
+  external JSNumber get initialArguments;
+  external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
+}
+
+@JS()
+external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);