| // Copyright (c) 2017, 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. | |
| // TODO(johnniwinther): Make this a separate library. | |
| part of dart2js.kernel.element_map; | |
| class KernelAnnotationProcessor implements AnnotationProcessor { | |
| final KernelToElementMapImpl elementMap; | |
| final NativeBasicDataBuilder _nativeBasicDataBuilder; | |
| KernelAnnotationProcessor(this.elementMap, this._nativeBasicDataBuilder); | |
| void extractNativeAnnotations(LibraryEntity library) { | |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; | |
| KCommonElements commonElements = elementMap.commonElements; | |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { | |
| String annotationName; | |
| for (ConstantValue value in elementEnvironment.getClassMetadata(cls)) { | |
| String name = readAnnotationName( | |
| cls, value, commonElements.nativeAnnotationClass); | |
| if (annotationName == null) { | |
| annotationName = name; | |
| } else if (name != null) { | |
| failedAt(cls, 'Too many name annotations.'); | |
| } | |
| } | |
| if (annotationName != null) { | |
| _nativeBasicDataBuilder.setNativeClassTagInfo(cls, annotationName); | |
| } | |
| }); | |
| } | |
| String getJsInteropName( | |
| Spannable spannable, Iterable<ConstantValue> metadata) { | |
| KCommonElements commonElements = elementMap.commonElements; | |
| String annotationName; | |
| for (ConstantValue value in metadata) { | |
| String name = readAnnotationName( | |
| spannable, value, commonElements.jsAnnotationClass, | |
| defaultValue: ''); | |
| if (annotationName == null) { | |
| annotationName = name; | |
| } else if (name != null) { | |
| // TODO(johnniwinther): This should be an error, not a crash. | |
| failedAt(spannable, 'Too many name annotations.'); | |
| } | |
| } | |
| return annotationName; | |
| } | |
| void checkFunctionParameters(FunctionEntity function) { | |
| if (function.parameterStructure.namedParameters.isNotEmpty) { | |
| elementMap.reporter.reportErrorMessage( | |
| function, | |
| MessageKind.JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS, | |
| {'method': function.name}); | |
| } | |
| } | |
| void extractJsInteropAnnotations(LibraryEntity library) { | |
| DiagnosticReporter reporter = elementMap.reporter; | |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; | |
| KCommonElements commonElements = elementMap.commonElements; | |
| String libraryName = getJsInteropName( | |
| library, elementEnvironment.getLibraryMetadata(library)); | |
| final bool isExplicitlylyJsLibrary = libraryName != null; | |
| bool isJsLibrary = isExplicitlylyJsLibrary; | |
| elementEnvironment.forEachLibraryMember(library, (MemberEntity member) { | |
| String memberName = getJsInteropName( | |
| library, elementEnvironment.getMemberMetadata(member)); | |
| if (member.isField) { | |
| if (memberName != null) { | |
| // TODO(34174): Disallow js-interop fields. | |
| /*reporter.reportErrorMessage( | |
| member, MessageKind.JS_INTEROP_FIELD_NOT_SUPPORTED);*/ | |
| } | |
| } else { | |
| FunctionEntity function = member; | |
| if (function.isExternal && isExplicitlylyJsLibrary) { | |
| // External members of explicit js-interop library are implicitly | |
| // js-interop members. | |
| memberName ??= function.name; | |
| } | |
| if (memberName != null) { | |
| if (!function.isExternal) { | |
| // TODO(johnniwinther): Disallow non-external js-interop members. | |
| /*reporter.reportErrorMessage( | |
| function, MessageKind.JS_INTEROP_NON_EXTERNAL_MEMBER);*/ | |
| } else { | |
| _nativeBasicDataBuilder.markAsJsInteropMember(function, memberName); | |
| checkFunctionParameters(function); | |
| // TODO(johnniwinther): It is unclear whether library can be | |
| // implicitly js-interop. For now we allow it. | |
| isJsLibrary = true; | |
| } | |
| } else if (function.isExternal && | |
| !commonElements.isExternalAllowed(function)) { | |
| reporter.reportErrorMessage( | |
| function, MessageKind.NON_NATIVE_EXTERNAL); | |
| } | |
| } | |
| }); | |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { | |
| Iterable<ConstantValue> metadata = | |
| elementEnvironment.getClassMetadata(cls); | |
| String className = getJsInteropName(cls, metadata); | |
| if (className != null) { | |
| bool isAnonymous = false; | |
| for (ConstantValue value in metadata) { | |
| if (isAnnotation(cls, value, commonElements.jsAnonymousClass)) { | |
| isAnonymous = true; | |
| break; | |
| } | |
| } | |
| // TODO(johnniwinther): Report an error if the class is anonymous but | |
| // has a non-empty name. | |
| _nativeBasicDataBuilder.markAsJsInteropClass(cls, | |
| name: className, isAnonymous: isAnonymous); | |
| // TODO(johnniwinther): It is unclear whether library can be implicitly | |
| // js-interop. For now we allow it. | |
| isJsLibrary = true; | |
| elementEnvironment.forEachLocalClassMember(cls, (MemberEntity member) { | |
| if (member.isField) { | |
| // TODO(34174): Disallow js-interop fields. | |
| /*reporter.reportErrorMessage( | |
| member, MessageKind.IMPLICIT_JS_INTEROP_FIELD_NOT_SUPPORTED);*/ | |
| } else { | |
| FunctionEntity function = member; | |
| String memberName = getJsInteropName( | |
| library, elementEnvironment.getMemberMetadata(function)); | |
| if (function.isExternal) { | |
| memberName ??= function.name; | |
| } | |
| if (memberName != null) { | |
| // TODO(johnniwinther): The documentation states that explicit | |
| // member name annotations are not allowed on instance members. | |
| _nativeBasicDataBuilder.markAsJsInteropMember( | |
| function, memberName); | |
| } | |
| if (!function.isExternal && | |
| !function.isAbstract && | |
| !function.isStatic) { | |
| reporter.reportErrorMessage( | |
| function, | |
| MessageKind.JS_INTEROP_CLASS_NON_EXTERNAL_MEMBER, | |
| {'cls': cls.name, 'member': member.name}); | |
| } | |
| checkFunctionParameters(function); | |
| } | |
| }); | |
| elementEnvironment.forEachConstructor(cls, | |
| (ConstructorEntity constructor) { | |
| String memberName = getJsInteropName( | |
| library, elementEnvironment.getMemberMetadata(constructor)); | |
| if (constructor.isExternal) { | |
| // TODO(johnniwinther): It should probably be an error to have a | |
| // no-name constructor without a @JS() annotation. | |
| memberName ??= constructor.name; | |
| } | |
| if (memberName != null) { | |
| // TODO(johnniwinther): The documentation states that explicit | |
| // member name annotations are not allowed on instance members. | |
| _nativeBasicDataBuilder.markAsJsInteropMember( | |
| constructor, memberName); | |
| } | |
| // TODO(33834): It is a breaking change (at least against in some of | |
| // our own tests) but JS-interop constructors should be required to be | |
| // external since we otherwise allow creating a Dart object that tries | |
| // to pass as a JS-interop class. | |
| /*if (!constructor.isExternal) { | |
| reporter.reportErrorMessage(constructor, | |
| MessageKind.JS_INTEROP_CLASS_NON_EXTERNAL_CONSTRUCTOR, { | |
| 'cls': cls.name, | |
| 'constructor': | |
| constructor.name.isEmpty ? '${cls.name}.' : constructor.name | |
| }); | |
| }*/ | |
| if (constructor.isFactoryConstructor && isAnonymous) { | |
| if (constructor.parameterStructure.positionalParameters > 0) { | |
| reporter.reportErrorMessage( | |
| constructor, | |
| MessageKind | |
| .JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS, | |
| {'cls': cls.name}); | |
| } | |
| } else { | |
| checkFunctionParameters(constructor); | |
| } | |
| }); | |
| } else { | |
| elementEnvironment.forEachLocalClassMember(cls, (MemberEntity member) { | |
| String memberName = getJsInteropName( | |
| library, elementEnvironment.getMemberMetadata(member)); | |
| if (memberName != null) { | |
| reporter.reportErrorMessage( | |
| member, MessageKind.JS_INTEROP_MEMBER_IN_NON_JS_INTEROP_CLASS); | |
| } else if (member is FunctionEntity) { | |
| if (member.isExternal && | |
| !commonElements.isExternalAllowed(member)) { | |
| reporter.reportErrorMessage( | |
| member, MessageKind.NON_NATIVE_EXTERNAL); | |
| } | |
| } | |
| }); | |
| elementEnvironment.forEachConstructor(cls, | |
| (ConstructorEntity constructor) { | |
| String memberName = getJsInteropName( | |
| library, elementEnvironment.getMemberMetadata(constructor)); | |
| if (memberName != null) { | |
| reporter.reportErrorMessage(constructor, | |
| MessageKind.JS_INTEROP_MEMBER_IN_NON_JS_INTEROP_CLASS); | |
| } else { | |
| if (constructor.isExternal && | |
| !commonElements.isExternalAllowed(constructor)) { | |
| reporter.reportErrorMessage( | |
| constructor, MessageKind.NON_NATIVE_EXTERNAL); | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| if (isJsLibrary) { | |
| // TODO(johnniwinther): It is unclear whether library can be implicitly | |
| // js-interop. For now we allow it and assume the empty name. | |
| libraryName ??= ''; | |
| _nativeBasicDataBuilder.markAsJsInteropLibrary(library, | |
| name: libraryName); | |
| } | |
| } | |
| @override | |
| void processJsInteropAnnotations( | |
| NativeBasicData nativeBasicData, NativeDataBuilder nativeDataBuilder) { | |
| DiagnosticReporter reporter = elementMap.reporter; | |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; | |
| KCommonElements commonElements = elementMap.commonElements; | |
| for (LibraryEntity library in elementEnvironment.libraries) { | |
| // Error checking for class inheritance must happen after the first pass | |
| // through all the classes because it is possible to declare a subclass | |
| // before a superclass that has not yet had "markJsInteropClass" called on | |
| // it. | |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { | |
| Iterable<ConstantValue> metadata = | |
| elementEnvironment.getClassMetadata(cls); | |
| String className = getJsInteropName(cls, metadata); | |
| if (className != null) { | |
| bool implementsJsJavaScriptObjectClass = false; | |
| elementEnvironment.forEachSupertype(cls, (InterfaceType supertype) { | |
| if (supertype.element == commonElements.jsJavaScriptObjectClass) { | |
| implementsJsJavaScriptObjectClass = true; | |
| } | |
| }); | |
| if (!implementsJsJavaScriptObjectClass) { | |
| reporter.reportErrorMessage( | |
| cls, MessageKind.JS_INTEROP_CLASS_CANNOT_EXTEND_DART_CLASS, { | |
| 'cls': cls.name, | |
| 'superclass': elementEnvironment.getSuperClass(cls).name | |
| }); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| } |