| // 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. |
| |
| // @dart = 2.10 |
| |
| 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) |
| : assert(annotationData != null); |
| |
| 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 = readAnnotationName(commonElements.dartTypes, spannable, |
| value, commonElements.jsAnnotationClass1, defaultValue: '') ?? |
| readAnnotationName(commonElements.dartTypes, spannable, value, |
| commonElements.jsAnnotationClass2, |
| 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 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 isExplicitlylyJsLibrary = libraryName != null; |
| bool isJsLibrary = isExplicitlylyJsLibrary; |
| |
| elementEnvironment.forEachLibraryMember(library, (MemberEntity member) { |
| ir.Member memberNode = elementMap.getMemberNode(member); |
| String memberName = annotationData.getJsInteropMemberName(memberNode); |
| 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); |
| // 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); |
| // 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; |
| 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); |
| } |
| } |
| } |