| // 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. | |
| import 'package:kernel/ast.dart' as ir; | |
| import '../common.dart'; | |
| import '../common/elements.dart'; | |
| import '../constants/values.dart'; | |
| import '../elements/entities.dart'; | |
| import '../ir/annotations.dart'; | |
| import '../js_backend/native_data.dart'; | |
| import '../native/resolver.dart'; | |
| import 'element_map.dart'; | |
| class KernelAnnotationProcessor { | |
| final KernelToElementMap elementMap; | |
| final NativeBasicDataBuilder _nativeBasicDataBuilder; | |
| final IrAnnotationData annotationData; | |
| KernelAnnotationProcessor( | |
| this.elementMap, | |
| this._nativeBasicDataBuilder, | |
| this.annotationData, | |
| ); | |
| void extractNativeAnnotations(LibraryEntity library) { | |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; | |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { | |
| ir.Class node = elementMap.getClassNode(cls); | |
| String? annotationName = annotationData.getNativeClassName(node); | |
| 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; | |
| List<ClassEntity?> jsAnnotationClasses = [ | |
| commonElements.jsAnnotationClass1, | |
| commonElements.jsAnnotationClass2, | |
| commonElements.jsAnnotationClass3, | |
| ]; | |
| for (ClassEntity? jsAnnotationClass in jsAnnotationClasses) { | |
| if (jsAnnotationClass != null) { | |
| name = readAnnotationName( | |
| commonElements.dartTypes, | |
| spannable, | |
| value, | |
| jsAnnotationClass, | |
| defaultValue: '', | |
| ); | |
| if (name != null) break; | |
| } | |
| } | |
| 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 extractJsInteropAnnotations(LibraryEntity library) { | |
| // Unused reporter, add back in if uncommenting report lines down below. | |
| // DiagnosticReporter reporter = elementMap.reporter; | |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; | |
| ir.Library libraryNode = elementMap.getLibraryNode(library); | |
| String? libraryName = annotationData.getJsInteropLibraryName(libraryNode); | |
| final bool isExplicitlyJsLibrary = libraryName != null; | |
| bool isJsLibrary = isExplicitlyJsLibrary; | |
| elementEnvironment.forEachLibraryMember(library, (MemberEntity member) { | |
| ir.Member memberNode = elementMap.getMemberNode(member); | |
| String? memberName = annotationData.getJsInteropMemberName(memberNode); | |
| if (member is FieldEntity) { | |
| if (memberName != null) { | |
| // TODO(34174): Disallow js-interop fields. | |
| /*reporter.reportErrorMessage( | |
| member, MessageKind.JS_INTEROP_FIELD_NOT_SUPPORTED);*/ | |
| } | |
| } else { | |
| FunctionEntity function = member as FunctionEntity; | |
| // We need this explicit check as object literal constructors in | |
| // extension types do not need an `@JS()` annotation on them, their | |
| // extension type, or their library. JS interop checks assert that the | |
| // only extension type interop member that has named parameters is an | |
| // object literal constructor. | |
| // TODO(54968): We should handle the lowering for object literal | |
| // constructors in the interop transformer somehow instead and avoid | |
| // assuming all such members are object literal constructors or | |
| // otherwise paying the cost to verify by indexing extension types. | |
| bool isObjectLiteralConstructor = | |
| (memberNode.isExtensionTypeMember && | |
| memberNode.function?.namedParameters.isNotEmpty == true); | |
| if (function.isExternal && | |
| (isExplicitlyJsLibrary || isObjectLiteralConstructor)) { | |
| // 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); | |
| // TODO(johnniwinther): It is unclear whether library can be | |
| // implicitly js-interop. For now we allow it. | |
| isJsLibrary = true; | |
| } | |
| } | |
| } | |
| }); | |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { | |
| ir.Class classNode = elementMap.getClassNode(cls); | |
| String? className = annotationData.getJsInteropClassName(classNode); | |
| if (className != null) { | |
| bool isAnonymous = annotationData.isAnonymousJsInteropClass(classNode); | |
| bool isStaticInterop = annotationData.isStaticInteropClass(classNode); | |
| // TODO(johnniwinther): Report an error if the class is anonymous but | |
| // has a non-empty name. | |
| _nativeBasicDataBuilder.markAsJsInteropClass( | |
| cls, | |
| name: className, | |
| isAnonymous: isAnonymous, | |
| isStaticInterop: isStaticInterop, | |
| ); | |
| // 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 is FieldEntity) { | |
| // TODO(34174): Disallow js-interop fields. | |
| /*reporter.reportErrorMessage( | |
| member, MessageKind.IMPLICIT_JS_INTEROP_FIELD_NOT_SUPPORTED);*/ | |
| } else { | |
| FunctionEntity function = member as FunctionEntity; | |
| ir.Member memberNode = elementMap.getMemberNode(member); | |
| // Members that are not annotated and not external will result in | |
| // null here. For example, the default constructor which is not | |
| // user-specified. | |
| String? memberName = annotationData.getJsInteropMemberName( | |
| memberNode, | |
| ); | |
| 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, | |
| ); | |
| } | |
| } | |
| }); | |
| 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, | |
| ); | |
| } | |
| }); | |
| } | |
| }); | |
| 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, | |
| ); | |
| } | |
| } | |
| } |