blob: 26c0c7dddd1440f5e7700a6b6a481a953b7147e0 [file] [log] [blame]
// Copyright (c) 2020, 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/kernel.dart';
/// Returns true iff the node has an `@JS(...)` annotation from `package:js` or
/// from the internal `dart:_js_annotations`.
bool hasJSInteropAnnotation(Annotatable a) =>
a.annotations.any(_isPublicJSAnnotation);
/// Returns true iff the node has an `@anonymous` annotation from `package:js`
/// or from the internal `dart:_js_annotations`.
bool hasAnonymousAnnotation(Annotatable a) =>
a.annotations.any(_isAnonymousAnnotation);
/// Returns true iff the node has an `@staticInterop` annotation from
/// `package:js` or from the internal `dart:_js_annotations`.
bool hasStaticInteropAnnotation(Annotatable a) =>
a.annotations.any(_isStaticInteropAnnotation);
/// If [a] has a `@JS('...')` annotation, returns the value inside the
/// parentheses.
///
/// If there is none or the class does not have a `@JS()` annotation, returns
/// an empty String.
String getJSName(Annotatable a) {
String jsClass = '';
for (var annotation in a.annotations) {
if (_isPublicJSAnnotation(annotation)) {
var jsClasses = _stringAnnotationValues(annotation);
if (jsClasses.length > 0) {
jsClass = jsClasses[0];
}
}
}
return jsClass;
}
/// If [a] has a `@Native('...')` annotation, returns the values inside the
/// parentheses.
///
/// If there are none or the class does not have a `@Native()` annotation,
/// returns an empty list. Unlike `@JS()`, the string within `@Native()` is
/// allowed to contain several classes separated by a `,`.
List<String> getNativeNames(Annotatable a) {
List<String> nativeClasses = [];
for (var annotation in a.annotations) {
if (_isNativeAnnotation(annotation)) {
nativeClasses.addAll(_stringAnnotationValues(annotation));
}
}
return nativeClasses;
}
final _packageJs = Uri.parse('package:js/js.dart');
final _internalJs = Uri.parse('dart:_js_annotations');
final _jsHelper = Uri.parse('dart:_js_helper');
/// Returns true if [value] is the interop annotation whose class is
/// [annotationClassName] from `package:js` or from `dart:_js_annotations`.
bool _isInteropAnnotation(Expression value, String annotationClassName) {
var c = _annotationClass(value);
return c != null &&
c.name == annotationClassName &&
(c.enclosingLibrary.importUri == _packageJs ||
c.enclosingLibrary.importUri == _internalJs);
}
bool _isPublicJSAnnotation(Expression value) =>
_isInteropAnnotation(value, 'JS');
bool _isAnonymousAnnotation(Expression value) =>
_isInteropAnnotation(value, '_Anonymous');
bool _isStaticInteropAnnotation(Expression value) =>
_isInteropAnnotation(value, '_StaticInterop');
/// Returns true if [value] is the `Native` annotation from `dart:_js_helper`.
bool _isNativeAnnotation(Expression value) {
var c = _annotationClass(value);
return c != null &&
c.name == 'Native' &&
c.enclosingLibrary.importUri == _jsHelper;
}
/// Returns the class of the instance referred to by metadata annotation [node].
///
/// For example:
///
/// - `@JS()` would return the "JS" class in "package:js".
/// - `@anonymous` would return the "_Anonymous" class in "package:js".
/// - `@staticInterop` would return the "_StaticInterop" class in "package:js".
/// - `@Native` would return the "Native" class in "dart:_js_helper".
///
/// This function works regardless of whether the CFE is evaluating constants,
/// or whether the constant is a field reference (such as "anonymous" above).
Class? _annotationClass(Expression node) {
if (node is ConstantExpression) {
var constant = node.constant;
if (constant is InstanceConstant) return constant.classNode;
} else if (node is ConstructorInvocation) {
return node.target.enclosingClass;
} else if (node is StaticGet) {
var type = node.target.getterType;
if (type is InterfaceType) return type.classNode;
}
return null;
}
/// Returns the string values inside of a metadata annotation [node].
///
/// For example:
/// - `@JS('Foo')` would return ['Foo'].
/// - `@Native('Foo,Bar')` would return ['Foo', 'Bar'].
///
/// [node] is expected to be an annotation with either StringConstants or
/// StringLiterals that can be made up of multiple values. If there are none,
/// this method returns an empty list. This method throws an assertion if there
/// are multiple arguments or a named arg in the annotation.
List<String> _stringAnnotationValues(Expression node) {
List<String> values = [];
if (node is ConstantExpression) {
var constant = node.constant;
if (constant is InstanceConstant) {
var argLength = constant.fieldValues.values.length;
if (argLength == 1) {
var value = constant.fieldValues.values.elementAt(0);
if (value is StringConstant) values.addAll(value.value.split(','));
} else if (argLength > 1) {
throw new ArgumentError('Method expects annotation with at most one '
'positional argument: $node.');
}
}
} else if (node is ConstructorInvocation) {
var argLength = node.arguments.positional.length;
if (argLength > 1 || node.arguments.named.length > 0) {
throw new ArgumentError('Method expects annotation with at most one '
'positional argument: $node.');
} else if (argLength == 1) {
var value = node.arguments.positional[0];
if (value is StringLiteral) values.addAll(value.value.split(','));
}
}
return values;
}