| // 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:_js_interop_checks/src/js_interop.dart'; | |
| import 'package:kernel/kernel.dart'; | |
| import 'kernel_helpers.dart'; | |
| /// Returns true if [library] is one of the [candidates]. | |
| /// The latter should be a list, e.g.,: ['dart:js', 'dart:_js_annotations']. | |
| bool _isLibrary(Library library, List<String> candidates) { | |
| var uri = library.importUri; | |
| var scheme = uri.scheme; | |
| var path = uri.pathSegments[0]; | |
| for (var candidate in candidates) { | |
| var pair = candidate.split(':'); | |
| if (scheme == pair[0] && (path == pair[1] || pair[1] == '*')) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /// Returns true if [library] represents any library from `package:js`, | |
| /// `dart:_foreign_helper`, `dart:_js_annotations`, or `dart:_js_helper`, or | |
| /// `dart:js_interop`. | |
| bool _isJSLibrary(Library library) => _isLibrary(library, [ | |
| // While the annotations no longer live in `package:js`, this is needed to | |
| // support older versions of the package. | |
| 'package:js', | |
| 'dart:_foreign_helper', | |
| 'dart:_js_annotations', | |
| 'dart:_js_helper', | |
| // This is to allow `dart:js_interop`'s `@JS` to work with | |
| // `@staticInterop`. | |
| 'dart:js_interop', | |
| ]); | |
| /// Whether [node] is a direct call to `allowInterop`. | |
| bool isAllowInterop(Expression node) { | |
| if (node is StaticInvocation) { | |
| var target = node.target; | |
| return _isLibrary(target.enclosingLibrary, ['dart:js_util']) && | |
| target.name.text == 'allowInterop'; | |
| } | |
| return false; | |
| } | |
| bool isJsMember(Member member) { | |
| return member.isExternal && | |
| !_isLibrary(member.enclosingLibrary, ['dart:*']) && | |
| !isNative(member) && | |
| // Non-JS interop external members might exist e.g. the ones generated by | |
| // Angular, so we should check to see that this member actually uses JS | |
| // interop. | |
| usesJSInterop(member); | |
| } | |
| bool _annotationIsFromJSLibrary(String expectedName, Expression value) { | |
| var c = getAnnotationClass(value); | |
| return c != null && | |
| c.name == expectedName && | |
| _isJSLibrary(c.enclosingLibrary); | |
| } | |
| /// Whether [value] is a `@rest` annotation (to be used on function parameters | |
| /// to have them compiled as `...` rest params in ES6 outputs). | |
| bool isJsRestAnnotation(Expression value) => | |
| _annotationIsFromJSLibrary('_Rest', value); | |
| // TODO(jmesserly): Move JsPeerInterface to package:js (see issue #135). | |
| // TODO(jacobr): The 'JS' annotation is the new, publicly accessible one. | |
| // The 'JsName' annotation is the old one using internally by dart2js and | |
| // html libraries. These two concepts will probably merge eventually. | |
| bool isJSAnnotation(Expression value) => | |
| _annotationIsFromJSLibrary('JS', value) || isJSName(value); | |
| /// Returns [true] if [value] is the `JS` annotation from | |
| /// `package:js`, `dart:_js_annotations`, or `dart:js_interop`. | |
| bool isJSInteropAnnotation(Expression value) => | |
| _annotationIsFromJSLibrary('JS', value); | |
| bool _isJSAnonymousAnnotation(Expression value) => | |
| _annotationIsFromJSLibrary('_Anonymous', value); | |
| bool _isStaticInteropAnnotation(Expression value) => | |
| _annotationIsFromJSLibrary('_StaticInterop', value); | |
| /// Whether [value] is a `@JSExportName` (internal annotation used in SDK | |
| /// instead of `@JS` from `dart:_js_annotations`). | |
| bool isJSExportNameAnnotation(Expression value) => | |
| isBuiltinAnnotation(value, '_foreign_helper', 'JSExportName'); | |
| /// Whether [i] is a `spread` invocation (to be used on function arguments | |
| /// to have them compiled as `...` spread args in ES6 outputs). | |
| bool isJSSpreadInvocation(Procedure target) => | |
| target.name.text == 'spread' && _isJSLibrary(target.enclosingLibrary); | |
| bool isJSName(Expression value) => | |
| isBuiltinAnnotation(value, '_js_helper', 'JSName'); | |
| bool isJsPeerInterface(Expression value) => | |
| isBuiltinAnnotation(value, '_js_helper', 'JsPeerInterface'); | |
| bool isNativeAnnotation(Expression value) => | |
| isBuiltinAnnotation(value, '_js_helper', 'Native'); | |
| bool isJSAnonymousType(Class namedClass) { | |
| var hasJSInterop = hasJSInteropAnnotation(namedClass); | |
| var isAnonymous = | |
| findAnnotation(namedClass, _isJSAnonymousAnnotation) != null; | |
| return hasJSInterop && isAnonymous; | |
| } | |
| bool isStaticInteropType(Class namedClass) { | |
| var hasJSInterop = hasJSInteropAnnotation(namedClass); | |
| var isStaticInterop = | |
| findAnnotation(namedClass, _isStaticInteropAnnotation) != null; | |
| return hasJSInterop && isStaticInterop; | |
| } | |
| bool isUndefinedAnnotation(Expression value) => | |
| isBuiltinAnnotation(value, '_js_helper', '_Undefined'); | |
| /// Returns true iff the annotatable has an `@JS(...)` annotation from | |
| /// `package:js`, `dart:_js_annotations`, or `dart:js_interop`. | |
| /// | |
| /// Note: usually [usesJSInterop] should be used instead of this. | |
| // | |
| // TODO(jmesserly): I think almost all uses of this should be replaced with | |
| // [usesJSInterop], which also checks that the library is marked with `@JS`. | |
| // | |
| // Right now we have inconsistencies: sometimes we'll respect `@JS` on the | |
| // class itself, other places we require it on the library. Also members are | |
| // inconsistent: sometimes they need to have `@JS` on them, other times they | |
| // need to be `external` in an `@JS` class. | |
| // | |
| // TODO(srujzs): We should replace many of these helpers with the ones defined | |
| // in pkg:_js_interop_checks. | |
| bool hasJSInteropAnnotation(Annotatable a) => | |
| a.annotations.any(isJSInteropAnnotation); | |
| /// Returns true iff this element is a JS interop member. | |
| /// | |
| /// JS annotations are required explicitly on classes. Other elements, such as | |
| /// class members and top-level functions/accessors, should be marked `external` | |
| /// and should have directly or indirectly a `JS` annotation. It is sufficient | |
| /// if the annotation is in the procedure itself or an enclosing element like | |
| /// the class or library. | |
| bool usesJSInterop(NamedNode n) { | |
| if (n is Member && n.isExternal) { | |
| return hasJSInteropAnnotation(n) || | |
| (n.enclosingClass == null | |
| ? hasJSInteropAnnotation(n.enclosingLibrary) | |
| : hasJSInteropAnnotation(n.enclosingClass!)); | |
| } else if (n is Class) { | |
| return n.annotations.any(isJSInteropAnnotation); | |
| } | |
| return false; | |
| } | |
| /// Returns `true` when [member] is a JavaScript interop member using the | |
| /// package:js style of interop. | |
| /// | |
| /// Will return `false` when the newer static interop style is used or when | |
| /// [member] is not a JavaScript interop member at all. | |
| bool isNonStaticJsInterop(Member member) { | |
| return member.isExternal && | |
| !member.isExtensionMember && | |
| !member.isExtensionTypeMember && | |
| _usesNonStaticInteropAnnotation(member); | |
| } | |
| /// Returns `true` when [member] is a JavaScript interop member using the | |
| /// package:js style of interop annotation. | |
| /// | |
| /// This does not mean [member] has the annotation directly because it can | |
| /// be applied on the class or the library level. | |
| /// | |
| /// Will return `false` when the newer static interop style annotation is | |
| /// found or when [member] is not a JavaScript interop member at all. | |
| bool _usesNonStaticInteropAnnotation(Member member) { | |
| var enclosingClass = member.enclosingClass; | |
| if (enclosingClass != null) { | |
| return hasPackageJSAnnotation(enclosingClass) && | |
| !hasStaticInteropAnnotation(enclosingClass); | |
| } | |
| return hasPackageJSAnnotation(member) || | |
| hasPackageJSAnnotation(member.enclosingLibrary); | |
| } |