// 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 KernelToElementMapForImpactImpl elementMap; final NativeBasicDataBuilder _nativeBasicDataBuilder; KernelAnnotationProcessor(this.elementMap, this._nativeBasicDataBuilder); void extractNativeAnnotations(LibraryEntity library) { ElementEnvironment elementEnvironment = elementMap.elementEnvironment; CommonElements 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 metadata) { CommonElements 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; ElementEnvironment elementEnvironment = elementMap.elementEnvironment; CommonElements commonElements = elementMap.commonElements; String libraryName = getJsInteropName( library, elementEnvironment.getLibraryMetadata(library)); bool isJsLibrary = libraryName != null; elementEnvironment.forEachLibraryMember(library, (MemberEntity member) { if (member.isField) return; String memberName = getJsInteropName( library, elementEnvironment.getMemberMetadata(member)); if (memberName != null) { _nativeBasicDataBuilder.markAsJsInteropMember(member, memberName); checkFunctionParameters(member); } }); elementEnvironment.forEachClass(library, (ClassEntity cls) { Iterable 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): When fasta supports library metadata, report // and error if [isJsLibrary] is false. // For now, assume the library is a js-interop library. isJsLibrary = true; ClassEntity superclass = elementEnvironment.getSuperClass(cls); if (superclass != commonElements.jsJavaScriptObjectClass) { reporter.reportErrorMessage( cls, MessageKind.JS_INTEROP_CLASS_CANNOT_EXTEND_DART_CLASS, {'cls': cls.name, 'superclass': superclass.name}); } elementEnvironment.forEachClassMember(cls, (ClassEntity declarer, MemberEntity member) { if (declarer != cls) return; if (member.isField) return; FunctionEntity function = member; String memberName = getJsInteropName( library, elementEnvironment.getMemberMetadata(function)); if (memberName != null) { _nativeBasicDataBuilder.markAsJsInteropMember(function, memberName); } if (!function.isExternal && !function.isAbstract && !function.isConstructor && !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 (memberName != null) { _nativeBasicDataBuilder.markAsJsInteropMember( constructor, memberName); } if (!constructor.isExternal) { reporter.reportErrorMessage( constructor, MessageKind.JS_INTEROP_CLASS_NON_EXTERNAL_MEMBER, {'cls': cls.name, 'member': constructor.name}); } if (constructor.isFactoryConstructor && isAnonymous) { if (constructor.parameterStructure.requiredParameters > 0) { reporter.reportErrorMessage( constructor, MessageKind .JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS, {'cls': cls.name}); } } else { checkFunctionParameters(constructor); } }); } }); if (isJsLibrary) { // TODO(johnniwinther): Remove this when fasta supports library metadata. // For now, assume the empty name. libraryName ??= ''; _nativeBasicDataBuilder.markAsJsInteropLibrary(library, name: libraryName); } } @override void processJsInteropAnnotations( NativeBasicData nativeBasicData, NativeDataBuilder nativeDataBuilder) { // Nothing to do; all is computed in [extractJsInteropAnnotations]. } }