blob: 8eda873de7ebf92c6537a1560696cecbeb0c417a [file] [log] [blame]
// 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` or `createJSInteropWrapper`.
import 'dart:js_interop';
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.
// Same method with different name and type.
createJSInteropWrapper(ExportAll());
createJSInteropWrapper(ExportSome());
createJSInteropWrapper(NoAnnotations());
//^
// [web] Class 'NoAnnotations' does not have a `@JSExport` on it or any of its members.
createJSInteropWrapper(ExportNoneQualify());
createJSInteropWrapper(ExportEmpty());
createJSInteropWrapper(ExportWithNoExportSuperclass());
createJSInteropWrapper(ExportWithEmptyExportSuperclass());
createJSInteropWrapper(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.
createJSInteropWrapper<InvalidType>(() {});
//^
// [web] Type argument 'void Function()' needs to be an interface type.
// Can't use an interop class.
createJSInteropWrapper(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());
createJSInteropWrapper(RenameCollision());
createJSInteropWrapper(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());
createJSInteropWrapper(ClassWithValue());
}
// `JSExport` classes can't export methods that define type parameters as those
// type parameters will never be instantiated through interop. Class type
// parameters are okay, however.
@JSExport()
class GenericAll {
// ^
// [web] Class 'GenericAll' has no exportable members in the class or the inheritance chain.
void defineTypeParam<T extends int>() {}
T useTypeParam<T extends Object>(T t) => t;
}
class GenericSome<U> {
@JSExport()
void defineTypeParam<T extends int>() {}
// ^
// [web] Member 'defineTypeParam' is not a concrete instance member or declares type parameters, and therefore can't be exported.
T useTypeParam<T extends Object>(T t) => t;
@JSExport()
U useClassParam(U u) => u;
}
void testClassWithGenerics() {
createDartExport(GenericAll());
createDartExport(GenericSome());
createDartExport(GenericSome<int>());
createJSInteropWrapper(GenericAll());
createJSInteropWrapper(GenericSome());
createJSInteropWrapper(GenericSome<int>());
}
void main() {
testNumberOfExports();
testUseDartInterface();
testCollisions();
testClassExportWithValue();
testClassWithGenerics();
}