[pkg:js] Add tests for export validation
Tests the various checks needed for @JSExport and createDartExport,
with and without inheritance. Also renames the mock directory to export.
Change-Id: Iae0233f1080a2f12f20b46ba1a6b30aeb36843a8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260743
Reviewed-by: Riley Porter <rileyporter@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/tests/lib/js/export/export_inheritance_collision_test.dart b/tests/lib/js/export/export_inheritance_collision_test.dart
new file mode 100644
index 0000000..4d1fff6
--- /dev/null
+++ b/tests/lib/js/export/export_inheritance_collision_test.dart
@@ -0,0 +1,203 @@
+// 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.
+
+// Tests collisions of `@JSExport` members using inheritance.
+
+import 'package:js/js.dart';
+import 'package:js/js_util.dart';
+
+// Overridden members do not count as an export name collision.
+@JSExport()
+class SuperclassNoCollision {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+@JSExport()
+mixin MixinNoCollision {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+@JSExport()
+mixin MixinNoCollision2 {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+@JSExport()
+class InheritanceOneOverrideNoCollision extends SuperclassNoCollision
+ with MixinNoCollision {}
+
+@JSExport()
+class InheritanceTwoOverridesNoCollision extends SuperclassNoCollision
+ with MixinNoCollision, MixinNoCollision2 {}
+
+@JSExport()
+class InheritanceThreeOverridesNoCollision extends SuperclassNoCollision
+ with MixinNoCollision, MixinNoCollision2 {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+// Export name collisions can exist across classes and mixin applications.
+@JSExport()
+class SuperclassCollision {
+ @JSExport('field')
+ int fieldSuper = throw '';
+ @JSExport('finalField')
+ final int finalFieldSuper = throw '';
+ @JSExport('getSet')
+ int get getSetSuper => throw '';
+ @JSExport('getSet')
+ set getSetSuper(int val) => throw '';
+ @JSExport('method')
+ void methodSuper() => throw '';
+}
+
+@JSExport()
+mixin MixinCollision {
+ @JSExport('field')
+ int fieldMixin = throw '';
+ @JSExport('finalField')
+ final int finalFieldMixin = throw '';
+ @JSExport('getSet')
+ int get getSetMixin => throw '';
+ @JSExport('getSet')
+ set getSetMixin(int val) => throw '';
+ @JSExport('method')
+ void methodMixin() => throw '';
+}
+
+@JSExport()
+mixin MixinCollision2 {
+ @JSExport('field')
+ int fieldMixin2 = throw '';
+ @JSExport('finalField')
+ final int finalFieldMixin2 = throw '';
+ @JSExport('getSet')
+ int get getSetMixin2 => throw '';
+ @JSExport('getSet')
+ set getSetMixin2(int val) => throw '';
+ @JSExport('method')
+ void methodMixin2() => throw '';
+}
+
+@JSExport()
+class InheritanceRenameOneCollision extends SuperclassCollision
+// ^
+// [web] The following class members collide with the same export 'field': MixinCollision.fieldMixin, SuperclassCollision.fieldSuper.
+// [web] The following class members collide with the same export 'finalField': MixinCollision.finalFieldMixin, SuperclassCollision.finalFieldSuper.
+// [web] The following class members collide with the same export 'getSet': MixinCollision.getSetMixin, MixinCollision.getSetMixin, SuperclassCollision.getSetSuper, SuperclassCollision.getSetSuper.
+// [web] The following class members collide with the same export 'method': MixinCollision.methodMixin, SuperclassCollision.methodSuper.
+ with
+ MixinCollision {}
+
+@JSExport()
+class InheritanceRenameTwoCollisions extends SuperclassCollision
+// ^
+// [web] The following class members collide with the same export 'field': MixinCollision.fieldMixin, MixinCollision2.fieldMixin2, SuperclassCollision.fieldSuper.
+// [web] The following class members collide with the same export 'finalField': MixinCollision.finalFieldMixin, MixinCollision2.finalFieldMixin2, SuperclassCollision.finalFieldSuper.
+// [web] The following class members collide with the same export 'getSet': MixinCollision.getSetMixin, MixinCollision.getSetMixin, MixinCollision2.getSetMixin2, MixinCollision2.getSetMixin2, SuperclassCollision.getSetSuper, SuperclassCollision.getSetSuper.
+// [web] The following class members collide with the same export 'method': MixinCollision.methodMixin, MixinCollision2.methodMixin2, SuperclassCollision.methodSuper.
+ with
+ MixinCollision,
+ MixinCollision2 {}
+
+@JSExport()
+class InheritanceRenameThreeCollisions extends SuperclassCollision
+// ^
+// [web] The following class members collide with the same export 'field': InheritanceRenameThreeCollisions.fieldDerived, MixinCollision.fieldMixin, MixinCollision2.fieldMixin2, SuperclassCollision.fieldSuper.
+// [web] The following class members collide with the same export 'finalField': InheritanceRenameThreeCollisions.finalFieldDerived, MixinCollision.finalFieldMixin, MixinCollision2.finalFieldMixin2, SuperclassCollision.finalFieldSuper.
+// [web] The following class members collide with the same export 'getSet': InheritanceRenameThreeCollisions.getSetDerived, InheritanceRenameThreeCollisions.getSetDerived, MixinCollision.getSetMixin, MixinCollision.getSetMixin, MixinCollision2.getSetMixin2, MixinCollision2.getSetMixin2, SuperclassCollision.getSetSuper, SuperclassCollision.getSetSuper.
+// [web] The following class members collide with the same export 'method': InheritanceRenameThreeCollisions.methodDerived, MixinCollision.methodMixin, MixinCollision2.methodMixin2, SuperclassCollision.methodSuper.
+ with
+ MixinCollision,
+ MixinCollision2 {
+ @JSExport('field')
+ int fieldDerived = throw '';
+ @JSExport('finalField')
+ final int finalFieldDerived = throw '';
+ @JSExport('getSet')
+ int get getSetDerived => throw '';
+ @JSExport('getSet')
+ set getSetDerived(int val) => throw '';
+ @JSExport('method')
+ void methodDerived() => throw '';
+}
+
+// No collision if superclass doesn't contain any members marked for export.
+class SuperclassNoMembers {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+class InheritanceNoSuperclassMembers extends SuperclassNoMembers {
+ @JSExport('field')
+ int fieldDerived = throw '';
+ @JSExport('finalField')
+ final int finalFieldDerived = throw '';
+ @JSExport('getSet')
+ int get getSetDerived => throw '';
+ @JSExport('getSet')
+ set getSetDerived(int val) => throw '';
+ @JSExport('method')
+ void methodDerived() => throw '';
+}
+
+class Fields {
+ int getterField = throw '';
+ int setterField = throw '';
+}
+
+// These partial overrides are okay, as there's still one getter and one setter
+// per name.
+@JSExport()
+class PartialOverrideFieldNoCollision extends Fields {
+ set getterField(int val) => throw '';
+ int get setterField => throw '';
+}
+
+@JSExport()
+class GetSet {
+ int get getter => throw '';
+ set setter(int val) => throw '';
+}
+
+// Getters and setters through inheritance should be okay.
+@JSExport()
+class GetSetInheritanceNoCollision extends Fields {
+ int get setter => throw '';
+ set getter(int val) => throw '';
+}
+
+void main() {
+ createDartExport(InheritanceOneOverrideNoCollision());
+ createDartExport(InheritanceTwoOverridesNoCollision());
+ createDartExport(InheritanceThreeOverridesNoCollision());
+
+ createDartExport(InheritanceRenameOneCollision());
+ createDartExport(InheritanceRenameTwoCollisions());
+ createDartExport(InheritanceRenameThreeCollisions());
+
+ createDartExport(InheritanceNoSuperclassMembers());
+
+ createDartExport(PartialOverrideFieldNoCollision());
+ createDartExport(GetSetInheritanceNoCollision());
+}
diff --git a/tests/lib/js/export/export_validation_test.dart b/tests/lib/js/export/export_validation_test.dart
new file mode 100644
index 0000000..af2fab8
--- /dev/null
+++ b/tests/lib/js/export/export_validation_test.dart
@@ -0,0 +1,187 @@
+// 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.
+
+// Tests uses of `@JSExport` and `createDartExport`.
+
+import 'package:js/js.dart';
+import 'package:js/js_util.dart';
+
+// You can either have a @JSExport annotation on the entire class or select
+// members only, and they may contain members that are ignored.
+@JSExport()
+class ExportAll {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ int method() => throw '';
+}
+
+class ExportSome {
+ ExportSome();
+ factory ExportSome.factory() => ExportSome();
+
+ @JSExport()
+ int field = throw '';
+ final int finalField = throw '';
+ @JSExport()
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ @JSExport()
+ int method() => throw '';
+
+ static int staticField = throw '';
+ static void staticMethod() => throw '';
+}
+
+extension on ExportSome {
+ int extensionMethod() => throw '';
+
+ static int extensionStaticField = throw '';
+ static void extensionStaticMethod() => throw '';
+}
+
+// We should leave Dart classes with no exports alone unless used in
+// `createDartExport`.
+class NoAnnotations {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ int method() => throw '';
+}
+
+// If there is a `@JSExport` annotation, but no exportable members, it's
+// considered an error.
+@JSExport()
+abstract class ExportNoneQualify {
+// ^
+// [web] Class 'ExportNoneQualify' has no exportable members in the class or the inheritance chain.
+ factory ExportNoneQualify() => throw '';
+
+ abstract int abstractField;
+ void abstractMethod();
+
+ static int staticField = throw '';
+ static void staticMethod() => throw '';
+}
+
+extension on ExportNoneQualify {
+ int method() => throw '';
+
+ static int staticField = throw '';
+ static void staticMethod() => throw '';
+}
+
+@JSExport()
+class ExportEmpty {}
+// ^
+// [web] Class 'ExportEmpty' has no exportable members in the class or the inheritance chain.
+
+// These are errors, as there are no exportable members that have the annotation
+// on them or on their class.
+@JSExport()
+class ExportWithNoExportSuperclass extends NoAnnotations {}
+// ^
+// [web] Class 'ExportWithNoExportSuperclass' has no exportable members in the class or the inheritance chain.
+
+@JSExport()
+class ExportWithEmptyExportSuperclass extends ExportEmpty {}
+// ^
+// [web] Class 'ExportWithEmptyExportSuperclass' has no exportable members in the class or the inheritance chain.
+
+// This isn't an error to write, but it will be when you use it as part of
+// `createDartExport`.
+class NoExportWithExportSuperclass extends ExportAll {}
+
+void testNumberOfExports() {
+ createDartExport(ExportAll());
+ createDartExport(ExportSome());
+ createDartExport(NoAnnotations());
+//^
+// [web] Class 'NoAnnotations' does not have a `@JSExport` on it or any of its members.
+ createDartExport(ExportNoneQualify());
+ createDartExport(ExportEmpty());
+ createDartExport(ExportWithNoExportSuperclass());
+ createDartExport(ExportWithEmptyExportSuperclass());
+ createDartExport(NoExportWithExportSuperclass());
+//^
+// [web] Class 'NoExportWithExportSuperclass' does not have a `@JSExport` on it or any of its members.
+}
+
+@JS()
+@staticInterop
+class StaticInterop {
+ external factory StaticInterop();
+}
+
+typedef InvalidType = void Function();
+
+void testUseDartInterface() {
+ // Needs to be an interface type.
+ createDartExport<InvalidType>(() {});
+//^
+// [web] Type argument 'void Function()' needs to be an interface type.
+
+ // Can't use an interop class.
+ createDartExport(StaticInterop());
+//^
+// [web] Type argument 'StaticInterop' needs to be a non-JS interop type.
+}
+
+// Incompatible members can't have the same export name using renaming.
+@JSExport()
+class RenameCollision {
+// ^
+// [web] The following class members collide with the same export 'exportName': RenameCollision.exportName, RenameCollision.finalField, RenameCollision.getSet, RenameCollision.getSet, RenameCollision.method.
+ int exportName = throw '';
+ @JSExport('exportName')
+ final int finalField = throw '';
+ @JSExport('exportName')
+ int get getSet => throw '';
+ @JSExport('exportName')
+ set getSet(int val) => throw '';
+ @JSExport('exportName')
+ void method() => throw '';
+}
+
+// Allowed collisions are only between getters and setters.
+@JSExport()
+class GetSetNoCollision {
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+
+ @JSExport('renamedGetSet')
+ int get renamedGetter => throw '';
+ @JSExport('renamedGetSet')
+ set renamedSetter(int val) => throw '';
+}
+
+void testCollisions() {
+ createDartExport(RenameCollision());
+ createDartExport(GetSetNoCollision());
+}
+
+// Class annotation values are warnings, not values, so they don't show up in
+// static error tests.
+@JSExport('Invalid')
+class ClassWithValue {
+ int get getSet => throw '';
+}
+
+@JSExport('Invalid')
+mixin MixinWithValue {
+ int get getSet => throw '';
+}
+
+void testClassExportWithValue() {
+ createDartExport(ClassWithValue());
+}
+
+void main() {
+ testNumberOfExports();
+ testUseDartInterface();
+ testCollisions();
+ testClassExportWithValue();
+}
diff --git a/tests/lib/js/static_interop_test/mock/extension_conflict_test.dart b/tests/lib/js/export/extension_conflict_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/extension_conflict_test.dart
rename to tests/lib/js/export/extension_conflict_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/functional_test.dart b/tests/lib/js/export/functional_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/functional_test.dart
rename to tests/lib/js/export/functional_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/functional_test_lib.dart b/tests/lib/js/export/functional_test_lib.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/functional_test_lib.dart
rename to tests/lib/js/export/functional_test_lib.dart
diff --git a/tests/lib/js/static_interop_test/mock/incorrect_type_arguments_test.dart b/tests/lib/js/export/incorrect_type_arguments_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/incorrect_type_arguments_test.dart
rename to tests/lib/js/export/incorrect_type_arguments_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/inheritance_test.dart b/tests/lib/js/export/inheritance_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/inheritance_test.dart
rename to tests/lib/js/export/inheritance_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/missing_overrides_test.dart b/tests/lib/js/export/missing_overrides_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/missing_overrides_test.dart
rename to tests/lib/js/export/missing_overrides_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/mockito_test.dart b/tests/lib/js/export/mockito_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/mockito_test.dart
rename to tests/lib/js/export/mockito_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/proto_test.dart b/tests/lib/js/export/proto_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/proto_test.dart
rename to tests/lib/js/export/proto_test.dart
diff --git a/tests/lib/js/static_interop_test/mock/subtype_overrides_test.dart b/tests/lib/js/export/subtype_overrides_test.dart
similarity index 100%
rename from tests/lib/js/static_interop_test/mock/subtype_overrides_test.dart
rename to tests/lib/js/export/subtype_overrides_test.dart
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 25070be..43869c7 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -12,10 +12,10 @@
isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
[ $runtime == d8 ]
+js/export/proto_test: SkipByDesign # Uses dart:html.
js/js_util/javascriptobject_extensions_test: SkipByDesign # Uses dart:html.
js/static_interop_test/constants_test: SkipByDesign # Uses dart:html.
js/static_interop_test/futurevaluetype_test: SkipByDesign # Uses dart:html.
-js/static_interop_test/mock/proto_test: SkipByDesign # Uses dart:html.
js/static_interop_test/supertype_transform_test: SkipByDesign # Uses dart:html.
[ $runtime == dart_precompiled ]
diff --git a/tests/lib_2/js/export/export_inheritance_collision_test.dart b/tests/lib_2/js/export/export_inheritance_collision_test.dart
new file mode 100644
index 0000000..4d1fff6
--- /dev/null
+++ b/tests/lib_2/js/export/export_inheritance_collision_test.dart
@@ -0,0 +1,203 @@
+// 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.
+
+// Tests collisions of `@JSExport` members using inheritance.
+
+import 'package:js/js.dart';
+import 'package:js/js_util.dart';
+
+// Overridden members do not count as an export name collision.
+@JSExport()
+class SuperclassNoCollision {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+@JSExport()
+mixin MixinNoCollision {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+@JSExport()
+mixin MixinNoCollision2 {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+@JSExport()
+class InheritanceOneOverrideNoCollision extends SuperclassNoCollision
+ with MixinNoCollision {}
+
+@JSExport()
+class InheritanceTwoOverridesNoCollision extends SuperclassNoCollision
+ with MixinNoCollision, MixinNoCollision2 {}
+
+@JSExport()
+class InheritanceThreeOverridesNoCollision extends SuperclassNoCollision
+ with MixinNoCollision, MixinNoCollision2 {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+// Export name collisions can exist across classes and mixin applications.
+@JSExport()
+class SuperclassCollision {
+ @JSExport('field')
+ int fieldSuper = throw '';
+ @JSExport('finalField')
+ final int finalFieldSuper = throw '';
+ @JSExport('getSet')
+ int get getSetSuper => throw '';
+ @JSExport('getSet')
+ set getSetSuper(int val) => throw '';
+ @JSExport('method')
+ void methodSuper() => throw '';
+}
+
+@JSExport()
+mixin MixinCollision {
+ @JSExport('field')
+ int fieldMixin = throw '';
+ @JSExport('finalField')
+ final int finalFieldMixin = throw '';
+ @JSExport('getSet')
+ int get getSetMixin => throw '';
+ @JSExport('getSet')
+ set getSetMixin(int val) => throw '';
+ @JSExport('method')
+ void methodMixin() => throw '';
+}
+
+@JSExport()
+mixin MixinCollision2 {
+ @JSExport('field')
+ int fieldMixin2 = throw '';
+ @JSExport('finalField')
+ final int finalFieldMixin2 = throw '';
+ @JSExport('getSet')
+ int get getSetMixin2 => throw '';
+ @JSExport('getSet')
+ set getSetMixin2(int val) => throw '';
+ @JSExport('method')
+ void methodMixin2() => throw '';
+}
+
+@JSExport()
+class InheritanceRenameOneCollision extends SuperclassCollision
+// ^
+// [web] The following class members collide with the same export 'field': MixinCollision.fieldMixin, SuperclassCollision.fieldSuper.
+// [web] The following class members collide with the same export 'finalField': MixinCollision.finalFieldMixin, SuperclassCollision.finalFieldSuper.
+// [web] The following class members collide with the same export 'getSet': MixinCollision.getSetMixin, MixinCollision.getSetMixin, SuperclassCollision.getSetSuper, SuperclassCollision.getSetSuper.
+// [web] The following class members collide with the same export 'method': MixinCollision.methodMixin, SuperclassCollision.methodSuper.
+ with
+ MixinCollision {}
+
+@JSExport()
+class InheritanceRenameTwoCollisions extends SuperclassCollision
+// ^
+// [web] The following class members collide with the same export 'field': MixinCollision.fieldMixin, MixinCollision2.fieldMixin2, SuperclassCollision.fieldSuper.
+// [web] The following class members collide with the same export 'finalField': MixinCollision.finalFieldMixin, MixinCollision2.finalFieldMixin2, SuperclassCollision.finalFieldSuper.
+// [web] The following class members collide with the same export 'getSet': MixinCollision.getSetMixin, MixinCollision.getSetMixin, MixinCollision2.getSetMixin2, MixinCollision2.getSetMixin2, SuperclassCollision.getSetSuper, SuperclassCollision.getSetSuper.
+// [web] The following class members collide with the same export 'method': MixinCollision.methodMixin, MixinCollision2.methodMixin2, SuperclassCollision.methodSuper.
+ with
+ MixinCollision,
+ MixinCollision2 {}
+
+@JSExport()
+class InheritanceRenameThreeCollisions extends SuperclassCollision
+// ^
+// [web] The following class members collide with the same export 'field': InheritanceRenameThreeCollisions.fieldDerived, MixinCollision.fieldMixin, MixinCollision2.fieldMixin2, SuperclassCollision.fieldSuper.
+// [web] The following class members collide with the same export 'finalField': InheritanceRenameThreeCollisions.finalFieldDerived, MixinCollision.finalFieldMixin, MixinCollision2.finalFieldMixin2, SuperclassCollision.finalFieldSuper.
+// [web] The following class members collide with the same export 'getSet': InheritanceRenameThreeCollisions.getSetDerived, InheritanceRenameThreeCollisions.getSetDerived, MixinCollision.getSetMixin, MixinCollision.getSetMixin, MixinCollision2.getSetMixin2, MixinCollision2.getSetMixin2, SuperclassCollision.getSetSuper, SuperclassCollision.getSetSuper.
+// [web] The following class members collide with the same export 'method': InheritanceRenameThreeCollisions.methodDerived, MixinCollision.methodMixin, MixinCollision2.methodMixin2, SuperclassCollision.methodSuper.
+ with
+ MixinCollision,
+ MixinCollision2 {
+ @JSExport('field')
+ int fieldDerived = throw '';
+ @JSExport('finalField')
+ final int finalFieldDerived = throw '';
+ @JSExport('getSet')
+ int get getSetDerived => throw '';
+ @JSExport('getSet')
+ set getSetDerived(int val) => throw '';
+ @JSExport('method')
+ void methodDerived() => throw '';
+}
+
+// No collision if superclass doesn't contain any members marked for export.
+class SuperclassNoMembers {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ void method() => throw '';
+}
+
+class InheritanceNoSuperclassMembers extends SuperclassNoMembers {
+ @JSExport('field')
+ int fieldDerived = throw '';
+ @JSExport('finalField')
+ final int finalFieldDerived = throw '';
+ @JSExport('getSet')
+ int get getSetDerived => throw '';
+ @JSExport('getSet')
+ set getSetDerived(int val) => throw '';
+ @JSExport('method')
+ void methodDerived() => throw '';
+}
+
+class Fields {
+ int getterField = throw '';
+ int setterField = throw '';
+}
+
+// These partial overrides are okay, as there's still one getter and one setter
+// per name.
+@JSExport()
+class PartialOverrideFieldNoCollision extends Fields {
+ set getterField(int val) => throw '';
+ int get setterField => throw '';
+}
+
+@JSExport()
+class GetSet {
+ int get getter => throw '';
+ set setter(int val) => throw '';
+}
+
+// Getters and setters through inheritance should be okay.
+@JSExport()
+class GetSetInheritanceNoCollision extends Fields {
+ int get setter => throw '';
+ set getter(int val) => throw '';
+}
+
+void main() {
+ createDartExport(InheritanceOneOverrideNoCollision());
+ createDartExport(InheritanceTwoOverridesNoCollision());
+ createDartExport(InheritanceThreeOverridesNoCollision());
+
+ createDartExport(InheritanceRenameOneCollision());
+ createDartExport(InheritanceRenameTwoCollisions());
+ createDartExport(InheritanceRenameThreeCollisions());
+
+ createDartExport(InheritanceNoSuperclassMembers());
+
+ createDartExport(PartialOverrideFieldNoCollision());
+ createDartExport(GetSetInheritanceNoCollision());
+}
diff --git a/tests/lib_2/js/export/export_validation_test.dart b/tests/lib_2/js/export/export_validation_test.dart
new file mode 100644
index 0000000..af2fab8
--- /dev/null
+++ b/tests/lib_2/js/export/export_validation_test.dart
@@ -0,0 +1,187 @@
+// 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.
+
+// Tests uses of `@JSExport` and `createDartExport`.
+
+import 'package:js/js.dart';
+import 'package:js/js_util.dart';
+
+// You can either have a @JSExport annotation on the entire class or select
+// members only, and they may contain members that are ignored.
+@JSExport()
+class ExportAll {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ int method() => throw '';
+}
+
+class ExportSome {
+ ExportSome();
+ factory ExportSome.factory() => ExportSome();
+
+ @JSExport()
+ int field = throw '';
+ final int finalField = throw '';
+ @JSExport()
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ @JSExport()
+ int method() => throw '';
+
+ static int staticField = throw '';
+ static void staticMethod() => throw '';
+}
+
+extension on ExportSome {
+ int extensionMethod() => throw '';
+
+ static int extensionStaticField = throw '';
+ static void extensionStaticMethod() => throw '';
+}
+
+// We should leave Dart classes with no exports alone unless used in
+// `createDartExport`.
+class NoAnnotations {
+ int field = throw '';
+ final int finalField = throw '';
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+ int method() => throw '';
+}
+
+// If there is a `@JSExport` annotation, but no exportable members, it's
+// considered an error.
+@JSExport()
+abstract class ExportNoneQualify {
+// ^
+// [web] Class 'ExportNoneQualify' has no exportable members in the class or the inheritance chain.
+ factory ExportNoneQualify() => throw '';
+
+ abstract int abstractField;
+ void abstractMethod();
+
+ static int staticField = throw '';
+ static void staticMethod() => throw '';
+}
+
+extension on ExportNoneQualify {
+ int method() => throw '';
+
+ static int staticField = throw '';
+ static void staticMethod() => throw '';
+}
+
+@JSExport()
+class ExportEmpty {}
+// ^
+// [web] Class 'ExportEmpty' has no exportable members in the class or the inheritance chain.
+
+// These are errors, as there are no exportable members that have the annotation
+// on them or on their class.
+@JSExport()
+class ExportWithNoExportSuperclass extends NoAnnotations {}
+// ^
+// [web] Class 'ExportWithNoExportSuperclass' has no exportable members in the class or the inheritance chain.
+
+@JSExport()
+class ExportWithEmptyExportSuperclass extends ExportEmpty {}
+// ^
+// [web] Class 'ExportWithEmptyExportSuperclass' has no exportable members in the class or the inheritance chain.
+
+// This isn't an error to write, but it will be when you use it as part of
+// `createDartExport`.
+class NoExportWithExportSuperclass extends ExportAll {}
+
+void testNumberOfExports() {
+ createDartExport(ExportAll());
+ createDartExport(ExportSome());
+ createDartExport(NoAnnotations());
+//^
+// [web] Class 'NoAnnotations' does not have a `@JSExport` on it or any of its members.
+ createDartExport(ExportNoneQualify());
+ createDartExport(ExportEmpty());
+ createDartExport(ExportWithNoExportSuperclass());
+ createDartExport(ExportWithEmptyExportSuperclass());
+ createDartExport(NoExportWithExportSuperclass());
+//^
+// [web] Class 'NoExportWithExportSuperclass' does not have a `@JSExport` on it or any of its members.
+}
+
+@JS()
+@staticInterop
+class StaticInterop {
+ external factory StaticInterop();
+}
+
+typedef InvalidType = void Function();
+
+void testUseDartInterface() {
+ // Needs to be an interface type.
+ createDartExport<InvalidType>(() {});
+//^
+// [web] Type argument 'void Function()' needs to be an interface type.
+
+ // Can't use an interop class.
+ createDartExport(StaticInterop());
+//^
+// [web] Type argument 'StaticInterop' needs to be a non-JS interop type.
+}
+
+// Incompatible members can't have the same export name using renaming.
+@JSExport()
+class RenameCollision {
+// ^
+// [web] The following class members collide with the same export 'exportName': RenameCollision.exportName, RenameCollision.finalField, RenameCollision.getSet, RenameCollision.getSet, RenameCollision.method.
+ int exportName = throw '';
+ @JSExport('exportName')
+ final int finalField = throw '';
+ @JSExport('exportName')
+ int get getSet => throw '';
+ @JSExport('exportName')
+ set getSet(int val) => throw '';
+ @JSExport('exportName')
+ void method() => throw '';
+}
+
+// Allowed collisions are only between getters and setters.
+@JSExport()
+class GetSetNoCollision {
+ int get getSet => throw '';
+ set getSet(int val) => throw '';
+
+ @JSExport('renamedGetSet')
+ int get renamedGetter => throw '';
+ @JSExport('renamedGetSet')
+ set renamedSetter(int val) => throw '';
+}
+
+void testCollisions() {
+ createDartExport(RenameCollision());
+ createDartExport(GetSetNoCollision());
+}
+
+// Class annotation values are warnings, not values, so they don't show up in
+// static error tests.
+@JSExport('Invalid')
+class ClassWithValue {
+ int get getSet => throw '';
+}
+
+@JSExport('Invalid')
+mixin MixinWithValue {
+ int get getSet => throw '';
+}
+
+void testClassExportWithValue() {
+ createDartExport(ClassWithValue());
+}
+
+void main() {
+ testNumberOfExports();
+ testUseDartInterface();
+ testCollisions();
+ testClassExportWithValue();
+}
diff --git a/tests/lib_2/js/static_interop_test/mock/extension_conflict_test.dart b/tests/lib_2/js/export/extension_conflict_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/extension_conflict_test.dart
rename to tests/lib_2/js/export/extension_conflict_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/functional_test.dart b/tests/lib_2/js/export/functional_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/functional_test.dart
rename to tests/lib_2/js/export/functional_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/functional_test_lib.dart b/tests/lib_2/js/export/functional_test_lib.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/functional_test_lib.dart
rename to tests/lib_2/js/export/functional_test_lib.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/incorrect_type_arguments_test.dart b/tests/lib_2/js/export/incorrect_type_arguments_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/incorrect_type_arguments_test.dart
rename to tests/lib_2/js/export/incorrect_type_arguments_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/inheritance_test.dart b/tests/lib_2/js/export/inheritance_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/inheritance_test.dart
rename to tests/lib_2/js/export/inheritance_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/missing_overrides_test.dart b/tests/lib_2/js/export/missing_overrides_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/missing_overrides_test.dart
rename to tests/lib_2/js/export/missing_overrides_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/mockito_test.dart b/tests/lib_2/js/export/mockito_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/mockito_test.dart
rename to tests/lib_2/js/export/mockito_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/proto_test.dart b/tests/lib_2/js/export/proto_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/proto_test.dart
rename to tests/lib_2/js/export/proto_test.dart
diff --git a/tests/lib_2/js/static_interop_test/mock/subtype_overrides_test.dart b/tests/lib_2/js/export/subtype_overrides_test.dart
similarity index 100%
rename from tests/lib_2/js/static_interop_test/mock/subtype_overrides_test.dart
rename to tests/lib_2/js/export/subtype_overrides_test.dart
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index bf3222c..6357acb 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -12,10 +12,10 @@
isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
[ $runtime == d8 ]
+js/export/proto_test: SkipByDesign # Uses dart:html.
js/js_util/javascriptobject_extensions_test: SkipByDesign # Uses dart:html.
js/static_interop_test/constants_test: SkipByDesign # Uses dart:html.
js/static_interop_test/futurevaluetype_test: SkipByDesign # Uses dart:html.
-js/static_interop_test/mock/proto_test: SkipByDesign # Uses dart:html.
js/static_interop_test/supertype_transform_test: SkipByDesign # Uses dart:html.
[ $runtime == dart_precompiled ]