| // Copyright (c) 2016, 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. | |
| library jsinterop.internal_annotations_test; | |
| import 'package:expect/async_helper.dart'; | |
| import 'package:expect/expect.dart'; | |
| import 'package:compiler/src/common/elements.dart'; | |
| import 'package:compiler/src/compiler.dart'; | |
| import 'package:compiler/src/elements/entities.dart' show ClassEntity; | |
| import 'package:compiler/src/elements/names.dart'; | |
| import 'package:compiler/src/universe/class_hierarchy.dart'; | |
| import 'package:compiler/src/universe/selector.dart'; | |
| import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld; | |
| import '../helpers/element_lookup.dart'; | |
| import 'package:compiler/src/util/memory_compiler.dart'; | |
| void main() { | |
| asyncTest(() async { | |
| await testClasses('package:js/js.dart', 'dart:_js_annotations'); | |
| await testClasses('package:js/js.dart', 'package:js/js.dart'); | |
| await testClasses('dart:_js_annotations', 'dart:_js_annotations'); | |
| }); | |
| } | |
| testClasses(String import1, String import2) async { | |
| test( | |
| String mainSource, { | |
| List<String> directlyInstantiated = const <String>[], | |
| List<String> abstractlyInstantiated = const <String>[], | |
| List<String> indirectlyInstantiated = const <String>[], | |
| }) async { | |
| String mainFile = 'sdk/tests/web/native/main.dart'; | |
| Uri entryPoint = Uri.parse('memory:$mainFile'); | |
| CompilationResult result = await runCompiler( | |
| entryPoint: entryPoint, | |
| memorySourceFiles: { | |
| mainFile: | |
| """ | |
| import '$import1' as js1; | |
| import '$import2' as js2; | |
| @js1.JS() | |
| class A { | |
| external get foo; | |
| external A(foo); | |
| } | |
| @js2.JS('BClass') | |
| class B { | |
| external get foo; | |
| external B(foo); | |
| } | |
| @js1.JS() | |
| @js1.anonymous | |
| class C { | |
| external get foo; | |
| external factory C({foo}); | |
| } | |
| @js2.JS() | |
| @js2.anonymous | |
| class D { | |
| external get foo; | |
| external factory D({foo}); | |
| } | |
| class E { | |
| final foo; | |
| E(this.foo); | |
| } | |
| class F { | |
| final foo; | |
| F(this.foo); | |
| } | |
| newA() => A(0); | |
| newB() => B(1); | |
| newC() => C(foo: 2); | |
| newD() => D(foo: 3); | |
| newE() => E(4); | |
| newF() => F(5); | |
| $mainSource | |
| """, | |
| }, | |
| ); | |
| Compiler compiler = result.compiler!; | |
| Map<String, ClassEntity> classEnvironment = <String, ClassEntity>{}; | |
| ClassEntity registerClass(ClassEntity cls) { | |
| classEnvironment[cls.name] = cls; | |
| return cls; | |
| } | |
| JClosedWorld world = compiler.backendClosedWorldForTesting!; | |
| ElementEnvironment elementEnvironment = world.elementEnvironment; | |
| ClassEntity Object_ = registerClass(world.commonElements.objectClass); | |
| ClassEntity Interceptor = registerClass( | |
| world.commonElements.jsInterceptorClass, | |
| ); | |
| ClassEntity JavaScriptObject = registerClass( | |
| world.commonElements.jsJavaScriptObjectClass, | |
| ); | |
| ClassEntity LegacyJavaScriptObject = registerClass( | |
| world.commonElements.jsLegacyJavaScriptObjectClass, | |
| ); | |
| ClassEntity A = registerClass(findClass(world, 'A')); | |
| ClassEntity B = registerClass(findClass(world, 'B')); | |
| ClassEntity C = registerClass(findClass(world, 'C')); | |
| ClassEntity D = registerClass(findClass(world, 'D')); | |
| ClassEntity E = registerClass(findClass(world, 'E')); | |
| ClassEntity F = registerClass(findClass(world, 'F')); | |
| Selector nonExisting = Selector.getter(const PublicName('nonExisting')); | |
| Expect.equals(elementEnvironment.getSuperClass(Interceptor), Object_); | |
| Expect.equals( | |
| elementEnvironment.getSuperClass(JavaScriptObject), | |
| Interceptor, | |
| ); | |
| Expect.equals( | |
| elementEnvironment.getSuperClass(LegacyJavaScriptObject), | |
| JavaScriptObject, | |
| ); | |
| Expect.equals(elementEnvironment.getSuperClass(A), LegacyJavaScriptObject); | |
| Expect.equals(elementEnvironment.getSuperClass(B), LegacyJavaScriptObject); | |
| Expect.equals(elementEnvironment.getSuperClass(C), LegacyJavaScriptObject); | |
| Expect.equals(elementEnvironment.getSuperClass(D), LegacyJavaScriptObject); | |
| Expect.equals(elementEnvironment.getSuperClass(E), Object_); | |
| Expect.equals(elementEnvironment.getSuperClass(F), Object_); | |
| Expect.isFalse(world.nativeData.isJsInteropClass(Object_)); | |
| Expect.isTrue(world.nativeData.isJsInteropClass(A)); | |
| Expect.isTrue(world.nativeData.isJsInteropClass(B)); | |
| Expect.isTrue(world.nativeData.isJsInteropClass(C)); | |
| Expect.isTrue(world.nativeData.isJsInteropClass(D)); | |
| Expect.isFalse(world.nativeData.isJsInteropClass(E)); | |
| Expect.isFalse(world.nativeData.isJsInteropClass(F)); | |
| Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(Object_)); | |
| Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(A)); | |
| Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(B)); | |
| Expect.isTrue(world.nativeData.isAnonymousJsInteropClass(C)); | |
| Expect.isTrue(world.nativeData.isAnonymousJsInteropClass(D)); | |
| Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(E)); | |
| Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(F)); | |
| Expect.equals('', world.nativeData.getJsInteropClassName(A)); | |
| Expect.equals('BClass', world.nativeData.getJsInteropClassName(B)); | |
| Expect.equals('', world.nativeData.getJsInteropClassName(C)); | |
| Expect.equals('', world.nativeData.getJsInteropClassName(D)); | |
| classEnvironment.forEach((name, cls) { | |
| bool isInstantiated = false; | |
| if (directlyInstantiated.contains(name)) { | |
| isInstantiated = true; | |
| Expect.isTrue( | |
| world.classHierarchy.isDirectlyInstantiated(cls), | |
| "Expected $name to be directly instantiated in `${mainSource}`:" | |
| "\n${world.classHierarchy.dump(cls)}", | |
| ); | |
| } | |
| if (abstractlyInstantiated.contains(name)) { | |
| isInstantiated = true; | |
| Expect.isTrue( | |
| world.classHierarchy.isAbstractlyInstantiated(cls), | |
| "Expected $name to be abstractly instantiated in `${mainSource}`:" | |
| "\n${world.classHierarchy.dump(cls)}", | |
| ); | |
| Expect.isTrue( | |
| world.needsNoSuchMethod(cls, nonExisting, ClassQuery.exact), | |
| "Expected $name to need noSuchMethod for $nonExisting.", | |
| ); | |
| Expect.isTrue( | |
| world.needsNoSuchMethod(cls, nonExisting, ClassQuery.subclass), | |
| "Expected $name to need noSuchMethod for $nonExisting.", | |
| ); | |
| Expect.isTrue( | |
| world.needsNoSuchMethod(cls, nonExisting, ClassQuery.subtype), | |
| "Expected $name to need noSuchMethod for $nonExisting.", | |
| ); | |
| } | |
| if (indirectlyInstantiated.contains(name)) { | |
| isInstantiated = true; | |
| Expect.isTrue( | |
| world.classHierarchy.isIndirectlyInstantiated(cls), | |
| "Expected $name to be indirectly instantiated in `${mainSource}`:" | |
| "\n${world.classHierarchy.dump(cls)}", | |
| ); | |
| } | |
| // Classes that are expected to be instantiated by default. `Object` and | |
| // `Interceptor` are base types for non-native and native types, and | |
| // `JavaScriptObject` is the base type for `dart:html` types. | |
| var instantiatedBaseClasses = [ | |
| 'Object', | |
| 'Interceptor', | |
| 'JavaScriptObject', | |
| ]; | |
| if (!isInstantiated && !instantiatedBaseClasses.contains(name)) { | |
| Expect.isFalse( | |
| world.classHierarchy.isInstantiated(cls), | |
| "Expected $name to be uninstantiated in `${mainSource}`:" | |
| "\n${world.classHierarchy.dump(cls)}", | |
| ); | |
| } | |
| }); | |
| } | |
| await test('main() {}'); | |
| await test( | |
| 'main() => newA();', | |
| abstractlyInstantiated: ['A', 'B', 'C', 'D'], | |
| indirectlyInstantiated: [ | |
| 'Object', | |
| 'Interceptor', | |
| 'JavaScriptObject', | |
| 'LegacyJavaScriptObject', | |
| ], | |
| ); | |
| await test( | |
| 'main() => newB();', | |
| abstractlyInstantiated: ['A', 'B', 'C', 'D'], | |
| indirectlyInstantiated: [ | |
| 'Object', | |
| 'Interceptor', | |
| 'JavaScriptObject', | |
| 'LegacyJavaScriptObject', | |
| ], | |
| ); | |
| await test( | |
| 'main() => newC();', | |
| abstractlyInstantiated: ['A', 'B', 'C', 'D'], | |
| indirectlyInstantiated: [ | |
| 'Object', | |
| 'Interceptor', | |
| 'JavaScriptObject', | |
| 'LegacyJavaScriptObject', | |
| ], | |
| ); | |
| await test( | |
| 'main() => newD();', | |
| abstractlyInstantiated: ['A', 'B', 'C', 'D'], | |
| indirectlyInstantiated: [ | |
| 'Object', | |
| 'Interceptor', | |
| 'JavaScriptObject', | |
| 'LegacyJavaScriptObject', | |
| ], | |
| ); | |
| await test('main() => newE();', directlyInstantiated: ['E']); | |
| await test('main() => newF();', directlyInstantiated: ['F']); | |
| await test( | |
| 'main() => [newD(), newE()];', | |
| directlyInstantiated: ['E'], | |
| abstractlyInstantiated: ['A', 'B', 'C', 'D'], | |
| indirectlyInstantiated: [ | |
| 'Object', | |
| 'Interceptor', | |
| 'JavaScriptObject', | |
| 'LegacyJavaScriptObject', | |
| ], | |
| ); | |
| } |