mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 15:39:54 +00:00
[pkg:js] Add validation for @JSExport and createDartExport
Creates an external createDartExport function in js_util. Adds a number of checks for the annotation: - Classes with the annotation should not have value in the annotation - Classes with the annotation should have at least one instance member somewhere in the hierarchy - There are no export name collisions that are unresolvable accounting for overrides - Members with this annotation are instance members with a body only Also adds checks to createDartExport: - Checks that the type is a Dart class - Checks that the type is marked as exportable Change-Id: I52f27275966e9603e88921ce7897b7615178c4d2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259511 Reviewed-by: Riley Porter <rileyporter@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
parent
877713882c
commit
7f93985005
|
@ -6973,6 +6973,155 @@ const MessageCode messageJsInteropEnclosingClassJSAnnotationContext =
|
|||
severity: Severity.context,
|
||||
problemMessage: r"""This is the enclosing class.""");
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(String name)>
|
||||
templateJsInteropExportClassNotMarkedExportable =
|
||||
const Template<Message Function(String name)>(
|
||||
problemMessageTemplate:
|
||||
r"""Class '#name' does not have a `@JSExport` on it or any of its members.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Use the `@JSExport` annotation on this class.""",
|
||||
withArguments: _withArgumentsJsInteropExportClassNotMarkedExportable);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name)>
|
||||
codeJsInteropExportClassNotMarkedExportable =
|
||||
const Code<Message Function(String name)>(
|
||||
"JsInteropExportClassNotMarkedExportable",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportClassNotMarkedExportable(String name) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
return new Message(codeJsInteropExportClassNotMarkedExportable,
|
||||
problemMessage:
|
||||
"""Class '${name}' does not have a `@JSExport` on it or any of its members.""",
|
||||
correctionMessage: """Use the `@JSExport` annotation on this class.""",
|
||||
arguments: {'name': name});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(String name)>
|
||||
templateJsInteropExportDartInterfaceHasNonEmptyJSExportValue =
|
||||
const Template<Message Function(String name)>(
|
||||
problemMessageTemplate:
|
||||
r"""The value in the `@JSExport` annotation on the class or mixin '#name' will be ignored.""",
|
||||
correctionMessageTemplate: r"""Remove the value in the annotation.""",
|
||||
withArguments:
|
||||
_withArgumentsJsInteropExportDartInterfaceHasNonEmptyJSExportValue);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name)>
|
||||
codeJsInteropExportDartInterfaceHasNonEmptyJSExportValue =
|
||||
const Code<Message Function(String name)>(
|
||||
"JsInteropExportDartInterfaceHasNonEmptyJSExportValue",
|
||||
severity: Severity.warning);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportDartInterfaceHasNonEmptyJSExportValue(
|
||||
String name) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
return new Message(codeJsInteropExportDartInterfaceHasNonEmptyJSExportValue,
|
||||
problemMessage:
|
||||
"""The value in the `@JSExport` annotation on the class or mixin '${name}' will be ignored.""",
|
||||
correctionMessage: """Remove the value in the annotation.""",
|
||||
arguments: {'name': name});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
String
|
||||
name)> templateJsInteropExportDisallowedMember = const Template<
|
||||
Message Function(String name)>(
|
||||
problemMessageTemplate:
|
||||
r"""Member '#name' is not a concrete instance member, and therefore can't be exported.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Remove the `@JSExport` annotation from the member, and use an instance member to call this member instead.""",
|
||||
withArguments: _withArgumentsJsInteropExportDisallowedMember);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name)> codeJsInteropExportDisallowedMember =
|
||||
const Code<Message Function(String name)>(
|
||||
"JsInteropExportDisallowedMember",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportDisallowedMember(String name) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
return new Message(codeJsInteropExportDisallowedMember,
|
||||
problemMessage:
|
||||
"""Member '${name}' is not a concrete instance member, and therefore can't be exported.""",
|
||||
correctionMessage: """Remove the `@JSExport` annotation from the member, and use an instance member to call this member instead.""",
|
||||
arguments: {'name': name});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
String name,
|
||||
String
|
||||
string)> templateJsInteropExportMemberCollision = const Template<
|
||||
Message Function(String name, String string)>(
|
||||
problemMessageTemplate:
|
||||
r"""The following class members collide with the same export '#name': #string.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Either remove the conflicting members or use a different export name.""",
|
||||
withArguments: _withArgumentsJsInteropExportMemberCollision);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name, String string)>
|
||||
codeJsInteropExportMemberCollision =
|
||||
const Code<Message Function(String name, String string)>(
|
||||
"JsInteropExportMemberCollision",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportMemberCollision(
|
||||
String name, String string) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
if (string.isEmpty) throw 'No string provided';
|
||||
return new Message(codeJsInteropExportMemberCollision,
|
||||
problemMessage:
|
||||
"""The following class members collide with the same export '${name}': ${string}.""",
|
||||
correctionMessage: """Either remove the conflicting members or use a different export name.""",
|
||||
arguments: {'name': name, 'string': string});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
String
|
||||
name)> templateJsInteropExportNoExportableMembers = const Template<
|
||||
Message Function(String name)>(
|
||||
problemMessageTemplate:
|
||||
r"""Class '#name' has no exportable members in the class or the inheritance chain.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Using `@JSExport`, annotate at least one instance member with a body or annotate a class that has such a member in the inheritance chain.""",
|
||||
withArguments: _withArgumentsJsInteropExportNoExportableMembers);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name)>
|
||||
codeJsInteropExportNoExportableMembers =
|
||||
const Code<Message Function(String name)>(
|
||||
"JsInteropExportNoExportableMembers",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportNoExportableMembers(String name) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
return new Message(codeJsInteropExportNoExportableMembers,
|
||||
problemMessage:
|
||||
"""Class '${name}' has no exportable members in the class or the inheritance chain.""",
|
||||
correctionMessage: """Using `@JSExport`, annotate at least one instance member with a body or annotate a class that has such a member in the inheritance chain.""",
|
||||
arguments: {'name': name});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> codeJsInteropExternalExtensionMemberOnTypeInvalid =
|
||||
messageJsInteropExternalExtensionMemberOnTypeInvalid;
|
||||
|
|
|
@ -28,12 +28,14 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
|||
templateJsInteropNativeClassInAnnotation,
|
||||
templateJsInteropStaticInteropTrustTypesUsageNotAllowed,
|
||||
templateJsInteropStaticInteropTrustTypesUsedWithoutStaticInterop;
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_mock_creator.dart';
|
||||
|
||||
import 'src/js_interop.dart';
|
||||
|
||||
class JsInteropChecks extends RecursiveVisitor {
|
||||
final CoreTypes _coreTypes;
|
||||
final DiagnosticReporter<Message, LocatedMessage> _diagnosticsReporter;
|
||||
final ExportChecker exportChecker;
|
||||
final Map<String, Class> _nativeClasses;
|
||||
final _TypeParameterVisitor _typeParameterVisitor = _TypeParameterVisitor();
|
||||
bool _classHasJSAnnotation = false;
|
||||
|
@ -81,7 +83,9 @@ class JsInteropChecks extends RecursiveVisitor {
|
|||
bool _libraryIsGlobalNamespace = false;
|
||||
|
||||
JsInteropChecks(
|
||||
this._coreTypes, this._diagnosticsReporter, this._nativeClasses);
|
||||
this._coreTypes, this._diagnosticsReporter, this._nativeClasses)
|
||||
: exportChecker =
|
||||
ExportChecker(_diagnosticsReporter, _coreTypes.objectClass);
|
||||
|
||||
/// Extract all native class names from the [component].
|
||||
///
|
||||
|
@ -107,6 +111,7 @@ class JsInteropChecks extends RecursiveVisitor {
|
|||
if (!_isJSInteropMember(member)) _checkDisallowedExternal(member);
|
||||
// TODO(43530): Disallow having JS interop annotations on non-external
|
||||
// members (class members or otherwise). Currently, they're being ignored.
|
||||
exportChecker.visitMember(member);
|
||||
super.defaultMember(member);
|
||||
}
|
||||
|
||||
|
@ -206,6 +211,9 @@ class JsInteropChecks extends RecursiveVisitor {
|
|||
}
|
||||
}
|
||||
super.visitClass(cls);
|
||||
// Validate `@JSExport` usage after so we know if the members have the
|
||||
// annotation.
|
||||
exportChecker.visitClass(cls);
|
||||
_classHasAnonymousAnnotation = false;
|
||||
_classHasJSAnnotation = false;
|
||||
}
|
||||
|
@ -323,6 +331,7 @@ class JsInteropChecks extends RecursiveVisitor {
|
|||
procedure.fileUri);
|
||||
}
|
||||
}
|
||||
super.visitProcedure(procedure);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -366,6 +375,12 @@ class JsInteropChecks extends RecursiveVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExtension(Extension extension) {
|
||||
exportChecker.visitExtension(extension);
|
||||
super.visitExtension(extension);
|
||||
}
|
||||
|
||||
/// Reports an error if [functionNode] has named parameters.
|
||||
void _checkNoNamedParameters(FunctionNode functionNode) {
|
||||
// ignore: unnecessary_null_comparison
|
||||
|
|
|
@ -25,6 +25,11 @@ bool hasStaticInteropAnnotation(Annotatable a) =>
|
|||
bool hasTrustTypesAnnotation(Annotatable a) =>
|
||||
a.annotations.any(_isTrustTypesAnnotation);
|
||||
|
||||
/// Returns true iff the node has an `@JSExport(...)` annotation from
|
||||
/// `package:js` or from the internal `dart:_js_annotations`.
|
||||
bool hasJSExportAnnotation(Annotatable a) =>
|
||||
a.annotations.any(_isJSExportAnnotation);
|
||||
|
||||
/// If [a] has a `@JS('...')` annotation, returns the value inside the
|
||||
/// parentheses.
|
||||
///
|
||||
|
@ -59,6 +64,21 @@ List<String> getNativeNames(Annotatable a) {
|
|||
return nativeClasses;
|
||||
}
|
||||
|
||||
/// If [a] has a `@JSExport('...')` annotation, returns the value inside the
|
||||
/// parentheses.
|
||||
///
|
||||
/// If the class does not have a `@JSExport()` annotation, returns an empty
|
||||
/// String. Note that a value is guaranteed to exist.
|
||||
String getJSExportName(Annotatable a) {
|
||||
String jsExportValue = '';
|
||||
for (var annotation in a.annotations) {
|
||||
if (_isJSExportAnnotation(annotation)) {
|
||||
return _stringAnnotationValues(annotation)[0];
|
||||
}
|
||||
}
|
||||
return jsExportValue;
|
||||
}
|
||||
|
||||
final _packageJs = Uri.parse('package:js/js.dart');
|
||||
final _internalJs = Uri.parse('dart:_js_annotations');
|
||||
final _jsHelper = Uri.parse('dart:_js_helper');
|
||||
|
@ -86,6 +106,9 @@ bool _isStaticInteropAnnotation(Expression value) =>
|
|||
bool _isTrustTypesAnnotation(Expression value) =>
|
||||
_isInteropAnnotation(value, '_TrustTypes');
|
||||
|
||||
bool _isJSExportAnnotation(Expression value) =>
|
||||
_isInteropAnnotation(value, 'JSExport');
|
||||
|
||||
/// Returns true if [value] is the `Native` annotation from `dart:_js_helper`.
|
||||
bool _isNativeAnnotation(Expression value) {
|
||||
var c = _annotationClass(value);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
import 'package:front_end/src/fasta/fasta_codes.dart'
|
||||
show
|
||||
templateJsInteropExportInvalidInteropTypeArgument,
|
||||
templateJsInteropExportInvalidTypeArgument,
|
||||
templateJsInteropStaticInteropMockMemberNotSubtype,
|
||||
templateJsInteropStaticInteropMockNotDartInterfaceType,
|
||||
templateJsInteropStaticInteropMockNotStaticInteropType;
|
||||
|
@ -14,16 +16,190 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
|||
show
|
||||
Message,
|
||||
LocatedMessage,
|
||||
templateJsInteropExportClassNotMarkedExportable,
|
||||
templateJsInteropExportDartInterfaceHasNonEmptyJSExportValue,
|
||||
templateJsInteropExportDisallowedMember,
|
||||
templateJsInteropExportMemberCollision,
|
||||
templateJsInteropExportNoExportableMembers,
|
||||
templateJsInteropStaticInteropMockMissingOverride,
|
||||
templateJsInteropStaticInteropMockExternalExtensionMemberConflict;
|
||||
import 'package:_js_interop_checks/src/js_interop.dart' as js_interop;
|
||||
|
||||
class _ExtensionVisitor extends RecursiveVisitor {
|
||||
final Map<Reference, Extension> staticInteropClassesWithExtensions;
|
||||
enum _ExportStatus {
|
||||
EXPORT_ERROR,
|
||||
NON_EXPORTABLE,
|
||||
EXPORTABLE,
|
||||
}
|
||||
|
||||
_ExtensionVisitor(this.staticInteropClassesWithExtensions);
|
||||
class ExportChecker {
|
||||
final DiagnosticReporter<Message, LocatedMessage> _diagnosticReporter;
|
||||
final Map<Class, Map<String, Set<Member>>> exportClassToMemberMap = {};
|
||||
final Map<Class, _ExportStatus> exportStatus = {};
|
||||
final Class _objectClass;
|
||||
final Map<Class, Map<String, Member>> _overrideMap = {};
|
||||
final Map<Reference, Extension> staticInteropClassesWithExtensions = {};
|
||||
|
||||
@override
|
||||
ExportChecker(this._diagnosticReporter, this._objectClass);
|
||||
|
||||
/// Calculates the overrides, including inheritance, for [cls].
|
||||
///
|
||||
/// Note that we use a map from the unique name (with setter renaming) to
|
||||
/// avoid duplicate checks on classes, and to store the overrides.
|
||||
void _collectOverrides(Class cls) {
|
||||
if (_overrideMap.containsKey(cls)) return;
|
||||
Map<String, Member> memberMap;
|
||||
var superclass = cls.superclass;
|
||||
if (superclass != null && superclass != _objectClass) {
|
||||
_collectOverrides(superclass);
|
||||
memberMap = Map.from(_overrideMap[superclass]!);
|
||||
} else {
|
||||
memberMap = {};
|
||||
}
|
||||
// If this is a mixin application, fetch the members from the mixin.
|
||||
var demangledCls = cls.isMixinApplication ? cls.mixin : cls;
|
||||
for (var member in [
|
||||
...demangledCls.procedures.where((proc) => proc.exportable),
|
||||
...demangledCls.fields.where((field) => field.exportable)
|
||||
]) {
|
||||
var memberName = member.name.text;
|
||||
if (member is Procedure && member.isSetter) {
|
||||
memberMap[memberName + '='] = member;
|
||||
} else {
|
||||
if (member is Field && !member.isFinal) {
|
||||
memberMap[memberName + '='] = member;
|
||||
}
|
||||
memberMap[memberName] = member;
|
||||
}
|
||||
}
|
||||
_overrideMap[cls] = memberMap;
|
||||
}
|
||||
|
||||
/// Determine if [cls] is exportable, and if so, compute the export members.
|
||||
///
|
||||
///
|
||||
/// Check the following:
|
||||
/// - If the class has a `@JSExport` annotation, the value should be empty.
|
||||
/// - If the class has the annotation, it should have at least one exportable
|
||||
/// member in the class or in any superclass (ignoring `Object`).
|
||||
/// - Accounting for Dart overrides, the export member map of the class or
|
||||
/// any of its superclasses do not contain unresolvable name collisions. An
|
||||
/// explanation of the resolvable collisions is below.
|
||||
void visitClass(Class cls) {
|
||||
var classHasJSExport = js_interop.hasJSExportAnnotation(cls);
|
||||
// If the class doesn't have the annotation or if the class wasn't marked
|
||||
// when we visited the members and checked their annotations, there's
|
||||
// nothing to do for this class.
|
||||
if (!classHasJSExport && exportStatus[cls] != _ExportStatus.EXPORTABLE) {
|
||||
exportStatus[cls] = _ExportStatus.NON_EXPORTABLE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (classHasJSExport && js_interop.getJSExportName(cls).isNotEmpty) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportDartInterfaceHasNonEmptyJSExportValue
|
||||
.withArguments(cls.name),
|
||||
cls.fileOffset,
|
||||
cls.name.length,
|
||||
cls.location?.file);
|
||||
exportStatus[cls] = _ExportStatus.EXPORT_ERROR;
|
||||
}
|
||||
|
||||
_collectOverrides(cls);
|
||||
|
||||
var allExportableMembers = _overrideMap[cls]!.values.where((member) =>
|
||||
// Only members that qualify are those that are exportable, and either
|
||||
// their class has the annotation or they have it themselves.
|
||||
member.exportable &&
|
||||
(js_interop.hasJSExportAnnotation(member) ||
|
||||
js_interop.hasJSExportAnnotation(member.enclosingClass!)));
|
||||
var exports = <String, Set<Member>>{};
|
||||
|
||||
// Store the exportable members.
|
||||
for (var member in allExportableMembers) {
|
||||
var exportName = member.exportPropertyName;
|
||||
exports.putIfAbsent(exportName, () => {}).add(member);
|
||||
}
|
||||
|
||||
// Walk through the export map and determine if there are any unresolvable
|
||||
// conflicts.
|
||||
for (var exportName in exports.keys) {
|
||||
var existingMembers = exports[exportName]!;
|
||||
if (existingMembers.length == 1) continue;
|
||||
if (existingMembers.length == 2) {
|
||||
// There are two instances where you can resolve collisions:
|
||||
// 1. One of the members is a non-final field, and the other one is
|
||||
// either a strict getter or a strict setter that overrides part of
|
||||
// that field.
|
||||
// 2. One of the members is a strict getter, and the other one is a
|
||||
// strict setter or vice versa.
|
||||
// Any other case is an error to have more than 1 member per name.
|
||||
bool isCollisionOkay(Member m1, Member m2) {
|
||||
if (m1.isNonFinalField &&
|
||||
(m2.isStrictGetter || m2.isStrictSetter) &&
|
||||
// Is an override if the same name and across different classes.
|
||||
(m1.name.text == m2.name.text &&
|
||||
m1.enclosingClass != m2.enclosingClass)) {
|
||||
return true;
|
||||
} else if (m1.isStrictGetter && m2.isStrictSetter) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var first = existingMembers.elementAt(0);
|
||||
var second = existingMembers.elementAt(1);
|
||||
if (isCollisionOkay(first, second) || isCollisionOkay(second, first)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Sort to get deterministic order.
|
||||
var sortedExistingMembers =
|
||||
existingMembers.map((member) => member.toString()).toList()..sort();
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportMemberCollision.withArguments(
|
||||
exportName, sortedExistingMembers.join(', ')),
|
||||
cls.fileOffset,
|
||||
cls.name.length,
|
||||
cls.location?.file);
|
||||
exportStatus[cls] = _ExportStatus.EXPORT_ERROR;
|
||||
}
|
||||
|
||||
if (exports.isEmpty) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportNoExportableMembers.withArguments(cls.name),
|
||||
cls.fileOffset,
|
||||
cls.name.length,
|
||||
cls.location?.file);
|
||||
exportStatus[cls] = _ExportStatus.EXPORT_ERROR;
|
||||
}
|
||||
|
||||
exportClassToMemberMap[cls] = exports;
|
||||
exportStatus[cls] ??= _ExportStatus.EXPORTABLE;
|
||||
}
|
||||
|
||||
/// Check that the [member] can be exportable if it has an annotation, and if
|
||||
/// so, mark the enclosing class as exportable.
|
||||
void visitMember(Member member) {
|
||||
var memberHasJSExportAnnotation = js_interop.hasJSExportAnnotation(member);
|
||||
var cls = member.enclosingClass;
|
||||
if (memberHasJSExportAnnotation) {
|
||||
if (!member.exportable) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportDisallowedMember
|
||||
.withArguments(member.name.text),
|
||||
member.fileOffset,
|
||||
member.name.text.length,
|
||||
member.location?.file);
|
||||
if (cls != null) exportStatus[cls] = _ExportStatus.EXPORT_ERROR;
|
||||
} else {
|
||||
// Mark as exportable so we know that the class has an exportable member
|
||||
// when we process the class later.
|
||||
if (cls != null) exportStatus[cls] = _ExportStatus.EXPORTABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Store the [extension] if the on-type is a `@staticInterop` class.
|
||||
void visitExtension(Extension extension) {
|
||||
// TODO(srujzs): This code was written with the assumption there would be
|
||||
// one single extension per `@staticInterop` class. This is no longer true
|
||||
|
@ -35,29 +211,34 @@ class _ExtensionVisitor extends RecursiveVisitor {
|
|||
staticInteropClassesWithExtensions[onType.className] = extension;
|
||||
}
|
||||
}
|
||||
super.visitExtension(extension);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(srujzs): Rename this class and file to focus on exports. Separate out
|
||||
// the export creation, export validation, and mock validation into three
|
||||
// separate files to make this cleaner.
|
||||
class StaticInteropMockCreator extends Transformer {
|
||||
final Procedure _allowInterop;
|
||||
final Procedure _callMethod;
|
||||
final Procedure _createDartExport;
|
||||
final Procedure _createStaticInteropMock;
|
||||
final DiagnosticReporter<Message, LocatedMessage> _diagnosticReporter;
|
||||
late final _ExtensionVisitor _extensionVisitor;
|
||||
final ExportChecker _exportChecker;
|
||||
final InterfaceType _functionType;
|
||||
final Procedure _getProperty;
|
||||
final Procedure _globalThis;
|
||||
final InterfaceType _objectType;
|
||||
final Procedure _setProperty;
|
||||
final Map<Reference, Extension> _staticInteropClassesWithExtensions = {};
|
||||
final TypeEnvironment _typeEnvironment;
|
||||
|
||||
StaticInteropMockCreator(this._typeEnvironment, this._diagnosticReporter)
|
||||
StaticInteropMockCreator(
|
||||
this._typeEnvironment, this._diagnosticReporter, this._exportChecker)
|
||||
: _allowInterop = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js', 'allowInterop'),
|
||||
_callMethod = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', 'callMethod'),
|
||||
_createDartExport = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', 'createDartExport'),
|
||||
_createStaticInteropMock = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', 'createStaticInteropMock'),
|
||||
_functionType = _typeEnvironment.coreTypes.functionNonNullableRawType,
|
||||
|
@ -67,16 +248,19 @@ class StaticInteropMockCreator extends Transformer {
|
|||
.getTopLevelProcedure('dart:js_util', 'get:globalThis'),
|
||||
_objectType = _typeEnvironment.coreTypes.objectNonNullableRawType,
|
||||
_setProperty = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', 'setProperty') {
|
||||
_extensionVisitor = _ExtensionVisitor(_staticInteropClassesWithExtensions);
|
||||
}
|
||||
|
||||
void processExtensions(Library library) =>
|
||||
_extensionVisitor.visitLibrary(library);
|
||||
.getTopLevelProcedure('dart:js_util', 'setProperty');
|
||||
|
||||
@override
|
||||
TreeNode visitStaticInvocation(StaticInvocation node) {
|
||||
if (node.target == _createDartExport) {
|
||||
if (_verifyExportable(node)) {
|
||||
// TODO(srujzs): Create the export by refactoring `_createMock`. For
|
||||
// now, don't do anything.
|
||||
}
|
||||
return node;
|
||||
}
|
||||
if (node.target != _createStaticInteropMock) return node;
|
||||
|
||||
var typeArguments = node.arguments.types;
|
||||
assert(typeArguments.length == 2);
|
||||
var staticInteropType = typeArguments[0];
|
||||
|
@ -114,21 +298,12 @@ class StaticInteropMockCreator extends Transformer {
|
|||
|
||||
var dartMemberMap = <String, Member>{};
|
||||
for (var procedure in dartClass.allInstanceProcedures) {
|
||||
// We only care about concrete instance getters, setters, and methods.
|
||||
if (procedure.isAbstract ||
|
||||
procedure.isStatic ||
|
||||
procedure.isExtensionMember ||
|
||||
procedure.isFactory) {
|
||||
continue;
|
||||
}
|
||||
var name = procedure.name.text;
|
||||
// Add a suffix to differentiate getters and setters.
|
||||
if (procedure.isSetter) name += '=';
|
||||
dartMemberMap[name] = procedure;
|
||||
}
|
||||
for (var field in dartClass.allInstanceFields) {
|
||||
// We only care about concrete instance fields.
|
||||
if (field.isAbstract || field.isStatic) continue;
|
||||
var name = field.name.text;
|
||||
dartMemberMap[name] = field;
|
||||
if (!field.isFinal) {
|
||||
|
@ -144,7 +319,7 @@ class StaticInteropMockCreator extends Transformer {
|
|||
staticInteropClass.computeAllNonStaticExternalExtensionMembers(
|
||||
nameToDescriptors,
|
||||
descriptorToClass,
|
||||
_staticInteropClassesWithExtensions,
|
||||
_exportChecker.staticInteropClassesWithExtensions,
|
||||
_typeEnvironment);
|
||||
for (var descriptorName in nameToDescriptors.keys) {
|
||||
var descriptors = nameToDescriptors[descriptorName]!;
|
||||
|
@ -163,7 +338,8 @@ class StaticInteropMockCreator extends Transformer {
|
|||
var violations = <String>[];
|
||||
for (var descriptor in descriptorConflicts) {
|
||||
var cls = descriptorToClass[descriptor]!;
|
||||
var extension = _staticInteropClassesWithExtensions[cls.reference]!;
|
||||
var extension =
|
||||
_exportChecker.staticInteropClassesWithExtensions[cls.reference]!;
|
||||
var extensionName =
|
||||
extension.isUnnamedExtension ? 'unnamed' : extension.name;
|
||||
violations.add("'${cls.name}.$extensionName'");
|
||||
|
@ -308,6 +484,52 @@ class StaticInteropMockCreator extends Transformer {
|
|||
node, nameToDescriptors, descriptorToClass, dartMemberMap);
|
||||
}
|
||||
|
||||
/// Validate that the class provided via `createDartExport` can be exported
|
||||
/// safely.
|
||||
///
|
||||
/// Checks that:
|
||||
/// - Type argument is a valid Dart interface type.
|
||||
/// - Type argument is not a JS interop type.
|
||||
/// - Type argument was not marked as non-exportable.
|
||||
///
|
||||
/// If there were no errors with processing the class, returns true.
|
||||
/// Otherwise, returns false.
|
||||
bool _verifyExportable(StaticInvocation node) {
|
||||
var dartType = node.arguments.types[0];
|
||||
if (dartType is! InterfaceType) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportInvalidTypeArgument.withArguments(
|
||||
dartType, true),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
return false;
|
||||
}
|
||||
var dartClass = dartType.classNode;
|
||||
if (js_interop.hasJSInteropAnnotation(dartClass) ||
|
||||
js_interop.hasStaticInteropAnnotation(dartClass) ||
|
||||
js_interop.hasAnonymousAnnotation(dartClass)) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportInvalidInteropTypeArgument.withArguments(
|
||||
dartType, true),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
return false;
|
||||
}
|
||||
var exportStatus = _exportChecker.exportStatus[dartClass]!;
|
||||
if (exportStatus == _ExportStatus.NON_EXPORTABLE) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropExportClassNotMarkedExportable
|
||||
.withArguments(dartClass.name),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
return false;
|
||||
}
|
||||
return exportStatus == _ExportStatus.EXPORTABLE;
|
||||
}
|
||||
|
||||
TreeNode _createMock(
|
||||
StaticInvocation node,
|
||||
Map<String, List<ExtensionMemberDescriptor>> nameToDescriptors,
|
||||
|
@ -528,22 +750,18 @@ class StaticInteropMockCreator extends Transformer {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(srujzs): Remove once we refactor the mock conformance. For now, the
|
||||
// logic here is semi-duplicated above for exporting.
|
||||
extension _DartClassExtension on Class {
|
||||
List<Procedure> get allInstanceProcedures {
|
||||
var allProcs = <Procedure>[];
|
||||
Class? cls = this;
|
||||
// We only care about instance procedures that have a body.
|
||||
bool isInstanceProcedure(Procedure proc) =>
|
||||
!proc.isAbstract &&
|
||||
!proc.isStatic &&
|
||||
!proc.isExtensionMember &&
|
||||
!proc.isFactory;
|
||||
while (cls != null) {
|
||||
allProcs.addAll(cls.procedures.where(isInstanceProcedure));
|
||||
allProcs.addAll(cls.procedures.where((proc) => proc.exportable));
|
||||
// Mixin members override the given superclass' members, but are
|
||||
// overridden by the class' instance members, so they are inserted next.
|
||||
if (cls.isMixinApplication) {
|
||||
allProcs.addAll(cls.mixin.procedures.where(isInstanceProcedure));
|
||||
allProcs.addAll(cls.mixin.procedures.where((proc) => proc.exportable));
|
||||
}
|
||||
cls = cls.superclass;
|
||||
}
|
||||
|
@ -555,11 +773,10 @@ extension _DartClassExtension on Class {
|
|||
List<Field> get allInstanceFields {
|
||||
var allFields = <Field>[];
|
||||
Class? cls = this;
|
||||
bool isInstanceField(Field field) => !field.isAbstract && !field.isStatic;
|
||||
while (cls != null) {
|
||||
allFields.addAll(cls.fields.where(isInstanceField));
|
||||
allFields.addAll(cls.fields.where((field) => field.exportable));
|
||||
if (cls.isMixinApplication) {
|
||||
allFields.addAll(cls.mixin.fields.where(isInstanceField));
|
||||
allFields.addAll(cls.mixin.fields.where((field) => field.exportable));
|
||||
}
|
||||
cls = cls.superclass;
|
||||
}
|
||||
|
@ -675,3 +892,41 @@ extension ExtensionMemberDescriptorExtension on ExtensionMemberDescriptor {
|
|||
|
||||
bool get isExternal => (this.member.asProcedure).isExternal;
|
||||
}
|
||||
|
||||
extension ProcedureExtension on Procedure {
|
||||
// We only care about concrete instance procedures.
|
||||
bool get exportable =>
|
||||
!this.isAbstract &&
|
||||
!this.isStatic &&
|
||||
!this.isExtensionMember &&
|
||||
!this.isFactory &&
|
||||
!this.isExternal &&
|
||||
this.kind != ProcedureKind.Operator;
|
||||
}
|
||||
|
||||
extension FieldExtension on Field {
|
||||
// We only care about concrete instance fields.
|
||||
bool get exportable => !this.isAbstract && !this.isStatic && !this.isExternal;
|
||||
}
|
||||
|
||||
extension MemberExtension on Member {
|
||||
// Get the property name that this member will be exported as.
|
||||
String get exportPropertyName {
|
||||
var rename = js_interop.getJSExportName(this);
|
||||
return rename.isEmpty ? this.name.text : rename;
|
||||
}
|
||||
|
||||
bool get exportable =>
|
||||
(this is Procedure && (this as Procedure).exportable) ||
|
||||
(this is Field && (this as Field).exportable);
|
||||
|
||||
// Only a getter and not a setter.
|
||||
bool get isStrictGetter =>
|
||||
(this is Procedure && (this as Procedure).isGetter) ||
|
||||
(this is Field && (this as Field).isFinal);
|
||||
|
||||
// Only a setter and not a getter.
|
||||
bool get isStrictSetter => this is Procedure && (this as Procedure).isSetter;
|
||||
|
||||
bool get isNonFinalField => this is Field && !(this as Field).isFinal;
|
||||
}
|
||||
|
|
|
@ -156,15 +156,16 @@ class Dart2jsTarget extends Target {
|
|||
coreTypes,
|
||||
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
|
||||
_nativeClasses!);
|
||||
var staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy), diagnosticReporter);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
|
||||
// Cache extensions for entire component before creating mock.
|
||||
for (var library in libraries) {
|
||||
staticInteropMockCreator.processExtensions(library);
|
||||
}
|
||||
// Process and validate first before doing anything with exports.
|
||||
for (var library in libraries) {
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
}
|
||||
var staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy),
|
||||
diagnosticReporter,
|
||||
jsInteropChecks.exportChecker);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
|
||||
for (var library in libraries) {
|
||||
staticInteropMockCreator.visitLibrary(library);
|
||||
// TODO (rileyporter): Merge js_util optimizations with other lowerings
|
||||
// in the single pass in `transformations/lowering.dart`.
|
||||
|
|
|
@ -105,15 +105,16 @@ class WasmTarget extends Target {
|
|||
coreTypes,
|
||||
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
|
||||
_nativeClasses!);
|
||||
final staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy), diagnosticReporter);
|
||||
final jsUtilOptimizer = JsUtilWasmOptimizer(coreTypes, hierarchy);
|
||||
// Cache extensions for entire component before creating mock.
|
||||
for (Library library in interopDependentLibraries) {
|
||||
staticInteropMockCreator.processExtensions(library);
|
||||
}
|
||||
// Process and validate first before doing anything with exports.
|
||||
for (Library library in interopDependentLibraries) {
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
}
|
||||
final staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy),
|
||||
diagnosticReporter,
|
||||
jsInteropChecks.exportChecker);
|
||||
final jsUtilOptimizer = JsUtilWasmOptimizer(coreTypes, hierarchy);
|
||||
for (Library library in interopDependentLibraries) {
|
||||
staticInteropMockCreator.visitLibrary(library);
|
||||
jsUtilOptimizer.visitLibrary(library);
|
||||
}
|
||||
|
|
|
@ -170,16 +170,17 @@ class DevCompilerTarget extends Target {
|
|||
coreTypes,
|
||||
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
|
||||
_nativeClasses!);
|
||||
var staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy), diagnosticReporter);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
|
||||
// Cache extensions for entire component before creating mock.
|
||||
// Process and validate first before doing anything with exports.
|
||||
for (var library in libraries) {
|
||||
staticInteropMockCreator.processExtensions(library);
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
}
|
||||
var staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy),
|
||||
diagnosticReporter,
|
||||
jsInteropChecks.exportChecker);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
|
||||
for (var library in libraries) {
|
||||
_CovarianceTransformer(library).transform();
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
staticInteropMockCreator.visitLibrary(library);
|
||||
jsUtilOptimizer.visitLibrary(library);
|
||||
}
|
||||
|
|
|
@ -3658,6 +3658,70 @@ Message _withArgumentsInvalidReturnPartNullability(
|
|||
});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
templateJsInteropExportInvalidInteropTypeArgument = const Template<
|
||||
Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
problemMessageTemplate:
|
||||
r"""Type argument '#type' needs to be a non-JS interop type.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Use a non-JS interop class that uses `@JSExport` instead.""",
|
||||
withArguments: _withArgumentsJsInteropExportInvalidInteropTypeArgument);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
codeJsInteropExportInvalidInteropTypeArgument =
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
"JsInteropExportInvalidInteropTypeArgument",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportInvalidInteropTypeArgument(
|
||||
DartType _type, bool isNonNullableByDefault) {
|
||||
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
|
||||
List<Object> typeParts = labeler.labelType(_type);
|
||||
String type = typeParts.join();
|
||||
return new Message(codeJsInteropExportInvalidInteropTypeArgument,
|
||||
problemMessage:
|
||||
"""Type argument '${type}' needs to be a non-JS interop type.""" +
|
||||
labeler.originMessages,
|
||||
correctionMessage:
|
||||
"""Use a non-JS interop class that uses `@JSExport` instead.""",
|
||||
arguments: {'type': _type});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
templateJsInteropExportInvalidTypeArgument = const Template<
|
||||
Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
problemMessageTemplate:
|
||||
r"""Type argument '#type' needs to be an interface type.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Use a non-JS interop class that uses `@JSExport` instead.""",
|
||||
withArguments: _withArgumentsJsInteropExportInvalidTypeArgument);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
codeJsInteropExportInvalidTypeArgument =
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
"JsInteropExportInvalidTypeArgument",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropExportInvalidTypeArgument(
|
||||
DartType _type, bool isNonNullableByDefault) {
|
||||
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
|
||||
List<Object> typeParts = labeler.labelType(_type);
|
||||
String type = typeParts.join();
|
||||
return new Message(codeJsInteropExportInvalidTypeArgument,
|
||||
problemMessage:
|
||||
"""Type argument '${type}' needs to be an interface type.""" +
|
||||
labeler.originMessages,
|
||||
correctionMessage:
|
||||
"""Use a non-JS interop class that uses `@JSExport` instead.""",
|
||||
arguments: {'type': _type});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
|
|
|
@ -561,6 +561,20 @@ JsInteropDartClassExtendsJSClass/analyzerCode: Fail # Web compiler specific
|
|||
JsInteropDartClassExtendsJSClass/example: Fail # Web compiler specific
|
||||
JsInteropEnclosingClassJSAnnotation/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropEnclosingClassJSAnnotation/example: Fail # Web compiler specific
|
||||
JsInteropExportClassNotMarkedExportable/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportClassNotMarkedExportable/example: Fail # Web compiler specific
|
||||
JsInteropExportDartInterfaceHasNonEmptyJSExportValue/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportDartInterfaceHasNonEmptyJSExportValue/example: Fail # Web compiler specific
|
||||
JsInteropExportDisallowedMember/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportDisallowedMember/example: Fail # Web compiler specific
|
||||
JsInteropExportInvalidInteropTypeArgument/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportInvalidInteropTypeArgument/example: Fail # Web compiler specific
|
||||
JsInteropExportInvalidTypeArgument/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportInvalidTypeArgument/example: Fail # Web compiler specific
|
||||
JsInteropExportMemberCollision/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportMemberCollision/example: Fail # Web compiler specific
|
||||
JsInteropExportNoExportableMembers/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExportNoExportableMembers/example: Fail # Web compiler specific
|
||||
JsInteropExternalExtensionMemberOnTypeInvalid/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropExternalExtensionMemberOnTypeInvalid/example: Fail # Web compiler specific
|
||||
JsInteropExternalMemberNotJSAnnotated/analyzerCode: Fail # Web compiler specific
|
||||
|
|
|
@ -5207,6 +5207,35 @@ JsInteropEnclosingClassJSAnnotationContext:
|
|||
problemMessage: "This is the enclosing class."
|
||||
severity: CONTEXT
|
||||
|
||||
JsInteropExportClassNotMarkedExportable:
|
||||
problemMessage: "Class '#name' does not have a `@JSExport` on it or any of its members."
|
||||
correctionMessage: "Use the `@JSExport` annotation on this class."
|
||||
|
||||
JsInteropExportDartInterfaceHasNonEmptyJSExportValue:
|
||||
problemMessage: "The value in the `@JSExport` annotation on the class or mixin '#name' will be ignored."
|
||||
correctionMessage: "Remove the value in the annotation."
|
||||
severity: WARNING
|
||||
|
||||
JsInteropExportDisallowedMember:
|
||||
problemMessage: "Member '#name' is not a concrete instance member, and therefore can't be exported."
|
||||
correctionMessage: "Remove the `@JSExport` annotation from the member, and use an instance member to call this member instead."
|
||||
|
||||
JsInteropExportInvalidInteropTypeArgument:
|
||||
problemMessage: "Type argument '#type' needs to be a non-JS interop type."
|
||||
correctionMessage: "Use a non-JS interop class that uses `@JSExport` instead."
|
||||
|
||||
JsInteropExportInvalidTypeArgument:
|
||||
problemMessage: "Type argument '#type' needs to be an interface type."
|
||||
correctionMessage: "Use a non-JS interop class that uses `@JSExport` instead."
|
||||
|
||||
JsInteropExportMemberCollision:
|
||||
problemMessage: "The following class members collide with the same export '#name': #string."
|
||||
correctionMessage: "Either remove the conflicting members or use a different export name."
|
||||
|
||||
JsInteropExportNoExportableMembers:
|
||||
problemMessage: "Class '#name' has no exportable members in the class or the inheritance chain."
|
||||
correctionMessage: "Using `@JSExport`, annotate at least one instance member with a body or annotate a class that has such a member in the inheritance chain."
|
||||
|
||||
JsInteropExternalExtensionMemberOnTypeInvalid:
|
||||
problemMessage: "JS interop or Native class required for 'external' extension members."
|
||||
correctionMessage: "Try adding a JS interop annotation to the on type class of the extension."
|
||||
|
|
|
@ -23,6 +23,7 @@ augmentations
|
|||
augmented
|
||||
b
|
||||
c
|
||||
collide
|
||||
compilercontext.runincontext
|
||||
compilesdk
|
||||
constructor(s)
|
||||
|
@ -48,6 +49,7 @@ interact
|
|||
interop
|
||||
intervening
|
||||
js_util
|
||||
jsexport
|
||||
libraries.json
|
||||
list.filled
|
||||
loadlibrary
|
||||
|
|
|
@ -199,3 +199,40 @@ external Object? dartify(Object? o);
|
|||
/// implemented, as well as how conflicts are resolved (if they are resolvable).
|
||||
/// The semantics here tries to conform to the view type specification.
|
||||
external T createStaticInteropMock<T, U>(U dartMock, [Object? proto = null]);
|
||||
|
||||
/// DO NOT USE - THIS IS UNIMPLEMENTED.
|
||||
///
|
||||
/// Given a Dart object that is marked exportable, creates a JS object literal
|
||||
/// that forwards to that Dart class. Look at the `@JSExport` annotation to
|
||||
/// determine what constitutes "exportable" for a Dart class. The object literal
|
||||
/// will be a map of export names (which are either the written instance member
|
||||
/// names or their rename) to their respective Dart instance members.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// @JSExport()
|
||||
/// class ExportCounter {
|
||||
/// int value = 0;
|
||||
/// String stringify() => value.toString();
|
||||
/// }
|
||||
///
|
||||
/// @JS()
|
||||
/// @staticInterop
|
||||
/// class Counter {}
|
||||
///
|
||||
/// extension on Counter {
|
||||
/// external int get value;
|
||||
/// external set value(int val);
|
||||
/// external String stringify();
|
||||
/// }
|
||||
///
|
||||
/// ...
|
||||
///
|
||||
/// var export = ExportCounter();
|
||||
/// var counter = createDartExport(export) as Counter;
|
||||
/// export.value = 1;
|
||||
/// Expect.isTrue(counter.value, export.value);
|
||||
/// Expect.isTrue(counter.stringify(), export.stringify());
|
||||
/// ```
|
||||
external Object createDartExport<T extends Object>(T dartObject);
|
||||
|
|
Loading…
Reference in a new issue