| // Copyright (c) 2012, 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 'dart:async'; |
| import "package:expect/expect.dart"; |
| import "package:async_helper/async_helper.dart"; |
| import "package:compiler/src/dart2jslib.dart"; |
| import "package:compiler/src/elements/elements.dart"; |
| import "package:compiler/src/tree/tree.dart"; |
| import "mock_compiler.dart"; |
| import "mock_libraries.dart"; |
| import 'package:compiler/src/elements/modelx.dart'; |
| |
| Future<Compiler> applyPatch(String script, String patch, |
| {bool analyzeAll: false, |
| bool analyzeOnly: false, |
| bool runCompiler: false, |
| String main: "", |
| String patchVersion}) { |
| Map<String, String> core = <String, String>{'script': script}; |
| MockCompiler compiler = new MockCompiler.internal(coreSource: core, |
| analyzeAll: analyzeAll, |
| analyzeOnly: analyzeOnly, |
| patchVersion: patchVersion); |
| compiler.diagnosticHandler = createHandler(compiler, ''); |
| var uri = Uri.parse("patch:core"); |
| compiler.registerSource(uri, "$DEFAULT_PATCH_CORE_SOURCE\n$patch"); |
| var future; |
| if (runCompiler) { |
| future = compiler.runCompiler(null, main); |
| } else { |
| future = compiler.init(main); |
| } |
| return future.then((_) => compiler); |
| } |
| |
| void expectHasBody(compiler, ElementX element) { |
| var node = element.parseNode(compiler); |
| Expect.isNotNull(node, "Element isn't parseable, when a body was expected"); |
| Expect.isNotNull(node.body); |
| // If the element has a body it is either a Block or a Return statement, |
| // both with different begin and end tokens. |
| Expect.isTrue(node.body is Block || node.body is Return); |
| Expect.notEquals(node.body.getBeginToken(), node.body.getEndToken()); |
| } |
| |
| void expectHasNoBody(compiler, ElementX element) { |
| var node = element.parseNode(compiler); |
| Expect.isNotNull(node, "Element isn't parseable, when a body was expected"); |
| Expect.isFalse(node.hasBody()); |
| } |
| |
| Element ensure(compiler, |
| String name, |
| Element lookup(name), |
| {bool expectIsPatched: false, |
| bool expectIsPatch: false, |
| bool checkHasBody: false, |
| bool expectIsGetter: false, |
| bool expectIsFound: true, |
| bool expectIsRegular: false}) { |
| var element = lookup(name); |
| if (!expectIsFound) { |
| Expect.isNull(element); |
| return element; |
| } |
| Expect.isNotNull(element); |
| if (expectIsGetter) { |
| Expect.isTrue(element is AbstractFieldElement); |
| Expect.isNotNull(element.getter); |
| element = element.getter; |
| } |
| Expect.equals(expectIsPatched, element.isPatched, |
| 'Unexpected: $element.isPatched = ${element.isPatched}'); |
| if (expectIsPatched) { |
| Expect.isNull(element.origin); |
| Expect.isNotNull(element.patch); |
| |
| Expect.equals(element, element.declaration); |
| Expect.equals(element.patch, element.implementation); |
| |
| if (checkHasBody) { |
| expectHasNoBody(compiler, element); |
| expectHasBody(compiler, element.patch); |
| } |
| } else { |
| Expect.isTrue(element.isImplementation); |
| } |
| Expect.equals(expectIsPatch, element.isPatch); |
| if (expectIsPatch) { |
| Expect.isNotNull(element.origin); |
| Expect.isNull(element.patch); |
| |
| Expect.equals(element.origin, element.declaration); |
| Expect.equals(element, element.implementation); |
| |
| if (checkHasBody) { |
| expectHasBody(compiler, element); |
| expectHasNoBody(compiler, element.origin); |
| } |
| } else { |
| Expect.isTrue(element.isDeclaration); |
| } |
| if (expectIsRegular) { |
| Expect.isNull(element.origin); |
| Expect.isNull(element.patch); |
| |
| Expect.equals(element, element.declaration); |
| Expect.equals(element, element.implementation); |
| |
| if (checkHasBody) { |
| expectHasBody(compiler, element); |
| } |
| } |
| Expect.isFalse(element.isPatched && element.isPatch); |
| return element; |
| } |
| |
| testPatchFunction() { |
| asyncTest(() => applyPatch( |
| "external test();", |
| "@patch test() { return 'string'; } ").then((compiler) { |
| ensure(compiler, "test", compiler.coreLibrary.find, |
| expectIsPatched: true, checkHasBody: true); |
| ensure(compiler, "test", compiler.coreLibrary.patch.find, |
| expectIsPatch: true, checkHasBody: true); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| |
| testPatchVersioned() { |
| String oldPatch = "test(){return 'string';}"; |
| String newPatch = "test(){return 'new and improved string';}"; |
| |
| String patchSource = |
| """ |
| @patch_old $oldPatch |
| @patch_new $newPatch |
| """; |
| |
| test(String patchVersion, |
| {String patchText, |
| bool expectIsPatched: true, |
| String expectedError, |
| String defaultPatch: '', |
| String expectedInternalError}) { |
| asyncTest(() => applyPatch( |
| "external test();", |
| """ |
| $defaultPatch |
| $patchSource |
| """, |
| patchVersion: patchVersion).then((compiler) { |
| Element origin = |
| ensure(compiler, "test", compiler.coreLibrary.find, |
| expectIsPatched: expectIsPatched, checkHasBody: true); |
| if (expectIsPatched) { |
| AstElement patch = |
| ensure(compiler, "test", compiler.coreLibrary.patch.find, |
| expectIsPatch: true, checkHasBody: true); |
| Expect.equals(origin.patch, patch); |
| Expect.equals(patch.origin, origin); |
| Expect.equals(patchText, patch.node.toString()); |
| } |
| |
| compiler.analyzeElement(origin); |
| compiler.enqueuer.resolution.emptyDeferredTaskQueue(); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| if (expectedError != null) { |
| Expect.equals(expectedError, |
| compiler.errors[0].message.toString()); |
| } else { |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| } |
| }).catchError((error) { |
| if (expectedInternalError != null) { |
| Expect.equals( |
| 'Internal Error: $expectedInternalError', error.toString()); |
| } else { |
| throw error; |
| } |
| })); |
| } |
| |
| test('old', patchText: oldPatch); |
| test('new', patchText: newPatch); |
| test('unknown', expectIsPatched: false, |
| expectedError: 'External method without an implementation.'); |
| test('old', |
| defaultPatch: "@patch test(){}", |
| expectedInternalError: "Trying to patch a function more than once."); |
| } |
| |
| testPatchConstructor() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| external Class(); |
| } |
| """, |
| """ |
| @patch class Class { |
| @patch Class(); |
| } |
| """).then((compiler) { |
| var classOrigin = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| classOrigin.ensureResolved(compiler); |
| var classPatch = ensure(compiler, "Class", compiler.coreLibrary.patch.find, |
| expectIsPatch: true); |
| |
| Expect.equals(classPatch, classOrigin.patch); |
| Expect.equals(classOrigin, classPatch.origin); |
| |
| var constructorOrigin = ensure(compiler, "", |
| (name) => classOrigin.localLookup(name), |
| expectIsPatched: true); |
| var constructorPatch = ensure(compiler, "", |
| (name) => classPatch.localLookup(name), |
| expectIsPatch: true); |
| |
| Expect.equals(constructorPatch, constructorOrigin.patch); |
| Expect.equals(constructorOrigin, constructorPatch.origin); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testPatchRedirectingConstructor() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| Class(x) : this._(x, false); |
| |
| external Class._(x, y); |
| } |
| """, |
| r""" |
| @patch class Class { |
| @patch Class._(x, y) { print('$x,$y'); } |
| } |
| """).then((compiler) { |
| var classOrigin = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| classOrigin.ensureResolved(compiler); |
| |
| var classPatch = ensure(compiler, "Class", compiler.coreLibrary.patch.find, |
| expectIsPatch: true); |
| |
| Expect.equals(classOrigin, classPatch.origin); |
| Expect.equals(classPatch, classOrigin.patch); |
| |
| var constructorRedirecting = |
| ensure(compiler, "", |
| (name) => classOrigin.localLookup(name)); |
| var constructorOrigin = |
| ensure(compiler, "_", |
| (name) => classOrigin.localLookup(name), |
| expectIsPatched: true); |
| var constructorPatch = |
| ensure(compiler, "_", |
| (name) => classPatch.localLookup(name), |
| expectIsPatch: true); |
| Expect.equals(constructorOrigin, constructorPatch.origin); |
| Expect.equals(constructorPatch, constructorOrigin.patch); |
| |
| compiler.resolver.resolve(constructorRedirecting); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testPatchMember() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| external String toString(); |
| } |
| """, |
| """ |
| @patch class Class { |
| @patch String toString() => 'string'; |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| ensure(compiler, "Class", compiler.coreLibrary.patch.find, |
| expectIsPatch: true); |
| |
| ensure(compiler, "toString", container.lookupLocalMember, |
| expectIsPatched: true, checkHasBody: true); |
| ensure(compiler, "toString", container.patch.lookupLocalMember, |
| expectIsPatch: true, checkHasBody: true); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testPatchGetter() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| external int get field; |
| } |
| """, |
| """ |
| @patch class Class { |
| @patch int get field => 5; |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| ensure(compiler, |
| "field", |
| container.lookupLocalMember, |
| expectIsGetter: true, |
| expectIsPatched: true, |
| checkHasBody: true); |
| ensure(compiler, |
| "field", |
| container.patch.lookupLocalMember, |
| expectIsGetter: true, |
| expectIsPatch: true, |
| checkHasBody: true); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testRegularMember() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| void regular() {} |
| } |
| """, |
| """ |
| @patch class Class { |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| ensure(compiler, "Class", compiler.coreLibrary.patch.find, |
| expectIsPatch: true); |
| |
| ensure(compiler, "regular", container.lookupLocalMember, |
| checkHasBody: true, expectIsRegular: true); |
| ensure(compiler, "regular", container.patch.lookupLocalMember, |
| checkHasBody: true, expectIsRegular: true); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testGhostMember() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| } |
| """, |
| """ |
| @patch class Class { |
| void ghost() {} |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| ensure(compiler, "Class", compiler.coreLibrary.patch.find, |
| expectIsPatch: true); |
| |
| ensure(compiler, "ghost", container.lookupLocalMember, |
| expectIsFound: false); |
| ensure(compiler, "ghost", container.patch.lookupLocalMember, |
| checkHasBody: true, expectIsRegular: true); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testInjectFunction() { |
| asyncTest(() => applyPatch( |
| "", |
| "int _function() => 5;").then((compiler) { |
| ensure(compiler, |
| "_function", |
| compiler.coreLibrary.find, |
| expectIsFound: false); |
| ensure(compiler, |
| "_function", |
| compiler.coreLibrary.patch.find, |
| checkHasBody: true, expectIsRegular: true); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| Expect.isTrue(compiler.errors.isEmpty, |
| "Unexpected errors: ${compiler.errors}"); |
| })); |
| } |
| |
| testPatchSignatureCheck() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| external String method1(); |
| external void method2(String str); |
| external void method3(String s1); |
| external void method4([String str]); |
| external void method5({String str}); |
| external void method6({String str}); |
| external void method7([String s1]); |
| external void method8({String s1}); |
| external void method9(String str); |
| external void method10([String str]); |
| external void method11({String str}); |
| } |
| """, |
| """ |
| @patch class Class { |
| @patch int method1() => 0; |
| @patch void method2() {} |
| @patch void method3(String s2) {} |
| @patch void method4([String str, int i]) {} |
| @patch void method5() {} |
| @patch void method6([String str]) {} |
| @patch void method7([String s2]) {} |
| @patch void method8({String s2}) {} |
| @patch void method9(int str) {} |
| @patch void method10([int str]) {} |
| @patch void method11({int str}) {} |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.ensureResolved(compiler); |
| container.parseNode(compiler); |
| |
| void expect(String methodName, List infos, List errors) { |
| compiler.clearMessages(); |
| compiler.resolver.resolveMethodElement( |
| ensure(compiler, methodName, container.lookupLocalMember, |
| expectIsPatched: true, checkHasBody: true)); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(infos.length, compiler.infos.length, |
| "Unexpected infos: ${compiler.infos} on $methodName"); |
| for (int i = 0 ; i < infos.length ; i++) { |
| Expect.equals(infos[i], compiler.infos[i].message.kind); |
| } |
| Expect.equals(errors.length, compiler.errors.length, |
| "Unexpected errors: ${compiler.errors} on $methodName"); |
| for (int i = 0 ; i < errors.length ; i++) { |
| Expect.equals(errors[i], compiler.errors[i].message.kind); |
| } |
| } |
| |
| expect("method1", [], [MessageKind.PATCH_RETURN_TYPE_MISMATCH]); |
| expect("method2", [], |
| [MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH]); |
| expect("method3", [MessageKind.PATCH_POINT_TO_PARAMETER], |
| [MessageKind.PATCH_PARAMETER_MISMATCH]); |
| expect("method4", [], |
| [MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH]); |
| expect("method5", [], |
| [MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH]); |
| expect("method6", [], |
| [MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH]); |
| expect("method7", [MessageKind.PATCH_POINT_TO_PARAMETER], |
| [MessageKind.PATCH_PARAMETER_MISMATCH]); |
| expect("method8", [MessageKind.PATCH_POINT_TO_PARAMETER], |
| [MessageKind.PATCH_PARAMETER_MISMATCH]); |
| expect("method9", [MessageKind.PATCH_POINT_TO_PARAMETER], |
| [MessageKind.PATCH_PARAMETER_TYPE_MISMATCH]); |
| expect("method10", [MessageKind.PATCH_POINT_TO_PARAMETER], |
| [MessageKind.PATCH_PARAMETER_TYPE_MISMATCH]); |
| expect("method11", [MessageKind.PATCH_POINT_TO_PARAMETER], |
| [MessageKind.PATCH_PARAMETER_TYPE_MISMATCH]); |
| })); |
| } |
| |
| testExternalWithoutImplementationTopLevel() { |
| asyncTest(() => applyPatch( |
| """ |
| external void foo(); |
| """, |
| """ |
| // @patch void foo() {} |
| """).then((compiler) { |
| var function = ensure(compiler, "foo", compiler.coreLibrary.find); |
| compiler.resolver.resolve(function); |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| print('testExternalWithoutImplementationTopLevel:${compiler.errors}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == |
| MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION); |
| Expect.stringEquals('External method without an implementation.', |
| compiler.errors[0].message.toString()); |
| })); |
| } |
| |
| testExternalWithoutImplementationMember() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| external void foo(); |
| } |
| """, |
| """ |
| @patch class Class { |
| // @patch void foo() {} |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| |
| compiler.warnings.clear(); |
| compiler.errors.clear(); |
| compiler.resolver.resolveMethodElement( |
| ensure(compiler, "foo", container.lookupLocalMember)); |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| print('testExternalWithoutImplementationMember:${compiler.errors}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == |
| MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION); |
| Expect.stringEquals('External method without an implementation.', |
| compiler.errors[0].message.toString()); |
| })); |
| } |
| |
| testIsSubclass() { |
| asyncTest(() => applyPatch( |
| """ |
| class A {} |
| """, |
| """ |
| @patch class A {} |
| """).then((compiler) { |
| ClassElement cls = ensure(compiler, "A", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| ClassElement patch = cls.patch; |
| Expect.isTrue(cls != patch); |
| Expect.isTrue(cls.isSubclassOf(patch)); |
| Expect.isTrue(patch.isSubclassOf(cls)); |
| })); |
| } |
| |
| testPatchNonExistingTopLevel() { |
| asyncTest(() => applyPatch( |
| """ |
| // class Class {} |
| """, |
| """ |
| @patch class Class {} |
| """).then((compiler) { |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| print('testPatchNonExistingTopLevel:${compiler.errors}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXISTING); |
| })); |
| } |
| |
| testPatchNonExistingMember() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class {} |
| """, |
| """ |
| @patch class Class { |
| @patch void foo() {} |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| print('testPatchNonExistingMember:${compiler.errors}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXISTING); |
| })); |
| } |
| |
| testPatchNonPatchablePatch() { |
| asyncTest(() => applyPatch( |
| """ |
| external get foo; |
| """, |
| """ |
| @patch var foo; |
| """).then((compiler) { |
| ensure(compiler, "foo", compiler.coreLibrary.find); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| print('testPatchNonPatchablePatch:${compiler.errors}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NONPATCHABLE); |
| })); |
| } |
| |
| testPatchNonPatchableOrigin() { |
| asyncTest(() => applyPatch( |
| """ |
| external var foo; |
| """, |
| """ |
| @patch get foo => 0; |
| """).then((compiler) { |
| ensure(compiler, "foo", compiler.coreLibrary.find); |
| |
| Expect.isTrue(compiler.warnings.isEmpty, |
| "Unexpected warnings: ${compiler.warnings}"); |
| print('testPatchNonPatchableOrigin:${compiler.errors}'); |
| Expect.equals(2, compiler.errors.length); |
| Expect.equals( |
| MessageKind.EXTRANEOUS_MODIFIER, compiler.errors[0].message.kind); |
| Expect.equals( |
| // TODO(ahe): Eventually, this error should be removed as it will be |
| // handled by the regular parser. |
| MessageKind.PATCH_NONPATCHABLE, compiler.errors[1].message.kind); |
| })); |
| } |
| |
| testPatchNonExternalTopLevel() { |
| asyncTest(() => applyPatch( |
| """ |
| void foo() {} |
| """, |
| """ |
| @patch void foo() {} |
| """).then((compiler) { |
| print('testPatchNonExternalTopLevel.errors:${compiler.errors}'); |
| print('testPatchNonExternalTopLevel.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXTERNAL); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue(compiler.infos[0].message.kind == |
| MessageKind.PATCH_POINT_TO_FUNCTION); |
| })); |
| } |
| |
| testPatchNonExternalMember() { |
| asyncTest(() => applyPatch( |
| """ |
| class Class { |
| void foo() {} |
| } |
| """, |
| """ |
| @patch class Class { |
| @patch void foo() {} |
| } |
| """).then((compiler) { |
| var container = ensure(compiler, "Class", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| container.parseNode(compiler); |
| |
| print('testPatchNonExternalMember.errors:${compiler.errors}'); |
| print('testPatchNonExternalMember.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXTERNAL); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue(compiler.infos[0].message.kind == |
| MessageKind.PATCH_POINT_TO_FUNCTION); |
| })); |
| } |
| |
| testPatchNonClass() { |
| asyncTest(() => applyPatch( |
| """ |
| external void Class() {} |
| """, |
| """ |
| @patch class Class {} |
| """).then((compiler) { |
| print('testPatchNonClass.errors:${compiler.errors}'); |
| print('testPatchNonClass.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_CLASS); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue( |
| compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_CLASS); |
| })); |
| } |
| |
| testPatchNonGetter() { |
| asyncTest(() => applyPatch( |
| """ |
| external void foo() {} |
| """, |
| """ |
| @patch get foo => 0; |
| """).then((compiler) { |
| print('testPatchNonClass.errors:${compiler.errors}'); |
| print('testPatchNonClass.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_GETTER); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue( |
| compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_GETTER); |
| })); |
| } |
| |
| testPatchNoGetter() { |
| asyncTest(() => applyPatch( |
| """ |
| external set foo(var value) {} |
| """, |
| """ |
| @patch get foo => 0; |
| """).then((compiler) { |
| print('testPatchNonClass.errors:${compiler.errors}'); |
| print('testPatchNonClass.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NO_GETTER); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue( |
| compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_GETTER); |
| })); |
| } |
| |
| testPatchNonSetter() { |
| asyncTest(() => applyPatch( |
| """ |
| external void foo() {} |
| """, |
| """ |
| @patch set foo(var value) {} |
| """).then((compiler) { |
| print('testPatchNonClass.errors:${compiler.errors}'); |
| print('testPatchNonClass.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_SETTER); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue( |
| compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_SETTER); |
| })); |
| } |
| |
| testPatchNoSetter() { |
| asyncTest(() => applyPatch( |
| """ |
| external get foo; |
| """, |
| """ |
| @patch set foo(var value) {} |
| """).then((compiler) { |
| print('testPatchNonClass.errors:${compiler.errors}'); |
| print('testPatchNonClass.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NO_SETTER); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue( |
| compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_SETTER); |
| })); |
| } |
| |
| testPatchNonFunction() { |
| asyncTest(() => applyPatch( |
| """ |
| external get foo; |
| """, |
| """ |
| @patch void foo() {} |
| """).then((compiler) { |
| print('testPatchNonClass.errors:${compiler.errors}'); |
| print('testPatchNonClass.warnings:${compiler.warnings}'); |
| Expect.equals(1, compiler.errors.length); |
| Expect.isTrue( |
| compiler.errors[0].message.kind == MessageKind.PATCH_NON_FUNCTION); |
| Expect.equals(0, compiler.warnings.length); |
| Expect.equals(1, compiler.infos.length); |
| Expect.isTrue( |
| compiler.infos[0].message.kind == |
| MessageKind.PATCH_POINT_TO_FUNCTION); |
| })); |
| } |
| |
| testPatchAndSelector() { |
| asyncTest(() => applyPatch( |
| """ |
| class A { |
| external void clear(); |
| } |
| class B extends A { |
| } |
| """, |
| """ |
| @patch class A { |
| int method() => 0; |
| @patch void clear() {} |
| } |
| """, |
| main: """ |
| main () { |
| new A(); // ensure A and B are instantiated |
| new B(); |
| } |
| """, |
| runCompiler: true, analyzeOnly: true).then((Compiler compiler) { |
| World world = compiler.world; |
| |
| ClassElement cls = ensure(compiler, "A", compiler.coreLibrary.find, |
| expectIsPatched: true); |
| cls.ensureResolved(compiler); |
| |
| ensure(compiler, "method", cls.patch.lookupLocalMember, |
| checkHasBody: true, expectIsRegular: true); |
| |
| ensure(compiler, "clear", cls.lookupLocalMember, |
| checkHasBody: true, expectIsPatched: true); |
| |
| compiler.phase = Compiler.PHASE_DONE_RESOLVING; |
| |
| // Check that a method just in the patch class is a target for a |
| // typed selector. |
| Selector selector = new Selector.call('method', compiler.coreLibrary, 0); |
| TypedSelector typedSelector = new TypedSelector.exact(cls, selector, world); |
| FunctionElement method = cls.implementation.lookupLocalMember('method'); |
| method.computeSignature(compiler); |
| Expect.isTrue(selector.applies(method, world)); |
| Expect.isTrue(typedSelector.applies(method, world)); |
| |
| // Check that the declaration method in the declaration class is a target |
| // for a typed selector. |
| selector = new Selector.call('clear', compiler.coreLibrary, 0); |
| typedSelector = new TypedSelector.exact(cls, selector, world); |
| method = cls.lookupLocalMember('clear'); |
| method.computeSignature(compiler); |
| Expect.isTrue(selector.applies(method, world)); |
| Expect.isTrue(typedSelector.applies(method, world)); |
| |
| // Check that the declaration method in the declaration class is a target |
| // for a typed selector on a subclass. |
| cls = ensure(compiler, "B", compiler.coreLibrary.find); |
| cls.ensureResolved(compiler); |
| typedSelector = new TypedSelector.exact(cls, selector, world); |
| Expect.isTrue(selector.applies(method, world)); |
| Expect.isTrue(typedSelector.applies(method, world)); |
| })); |
| } |
| |
| void testAnalyzeAllInjectedMembers() { |
| void expect(String patchText, [expectedWarnings]) { |
| if (expectedWarnings == null) expectedWarnings = []; |
| if (expectedWarnings is! List) { |
| expectedWarnings = <MessageKind>[expectedWarnings]; |
| } |
| |
| asyncTest(() => applyPatch('', patchText, analyzeAll: true, |
| analyzeOnly: true).then((compiler) { |
| compiler.librariesToAnalyzeWhenRun = [Uri.parse('dart:core')]; |
| return compiler.runCompiler(null).then((_) { |
| compareWarningKinds(patchText, expectedWarnings, compiler.warnings); |
| }); |
| })); |
| } |
| |
| expect('String s = 0;', MessageKind.NOT_ASSIGNABLE); |
| expect('void method() { String s = 0; }', MessageKind.NOT_ASSIGNABLE); |
| expect(''' |
| class Class { |
| String s = 0; |
| } |
| ''', |
| MessageKind.NOT_ASSIGNABLE); |
| expect(''' |
| class Class { |
| void method() { |
| String s = 0; |
| } |
| } |
| ''', |
| MessageKind.NOT_ASSIGNABLE); |
| } |
| |
| void testEffectiveTarget() { |
| String origin = """ |
| class A { |
| A() : super(); |
| factory A.forward() = B.patchTarget; |
| factory A.forwardTwo() = B.reflectBack; |
| } |
| class B extends A { |
| B() : super(); |
| external B.patchTarget(); |
| external factory B.reflectBack(); |
| B.originTarget() : super(); |
| } |
| """; |
| String patch = """ |
| @patch class B { |
| B.patchTarget() : super(); |
| factory B.reflectBack() = B.originTarget; |
| } |
| """; |
| |
| asyncTest(() => applyPatch(origin, patch, analyzeAll: true, |
| analyzeOnly: true, runCompiler: true).then((compiler) { |
| ClassElement clsA = compiler.coreLibrary.find("A"); |
| ClassElement clsB = compiler.coreLibrary.find("B"); |
| |
| Selector forwardCall = new Selector.callConstructor("forward", |
| compiler.coreLibrary); |
| ConstructorElement forward = clsA.lookupConstructor(forwardCall); |
| ConstructorElement target = forward.effectiveTarget; |
| Expect.isTrue(target.isPatch); |
| Expect.equals("patchTarget", target.name); |
| |
| Selector forwardTwoCall = new Selector.callConstructor("forwardTwo", |
| compiler.coreLibrary); |
| ConstructorElement forwardTwo = clsA.lookupConstructor(forwardTwoCall); |
| target = forwardTwo.effectiveTarget; |
| Expect.isFalse(forwardTwo.isErroneous); |
| Expect.isFalse(target.isPatch); |
| Expect.equals("originTarget", target.name); |
| })); |
| } |
| |
| void testTypecheckPatchedMembers() { |
| String originText = "external void method();"; |
| String patchText = """ |
| @patch void method() { |
| String s = 0; |
| } |
| """; |
| asyncTest(() => applyPatch(originText, patchText, |
| analyzeAll: true, analyzeOnly: true).then((compiler) { |
| compiler.librariesToAnalyzeWhenRun = [Uri.parse('dart:core')]; |
| return compiler.runCompiler(null).then((_) { |
| compareWarningKinds(patchText, |
| [MessageKind.NOT_ASSIGNABLE], compiler.warnings); |
| }); |
| })); |
| } |
| |
| main() { |
| testPatchConstructor(); |
| testPatchRedirectingConstructor(); |
| testPatchFunction(); |
| testPatchMember(); |
| testPatchGetter(); |
| testRegularMember(); |
| testGhostMember(); |
| testInjectFunction(); |
| testPatchSignatureCheck(); |
| |
| testPatchVersioned(); |
| |
| testExternalWithoutImplementationTopLevel(); |
| testExternalWithoutImplementationMember(); |
| |
| testIsSubclass(); |
| |
| testPatchNonExistingTopLevel(); |
| testPatchNonExistingMember(); |
| testPatchNonPatchablePatch(); |
| testPatchNonPatchableOrigin(); |
| testPatchNonExternalTopLevel(); |
| testPatchNonExternalMember(); |
| testPatchNonClass(); |
| testPatchNonGetter(); |
| testPatchNoGetter(); |
| testPatchNonSetter(); |
| testPatchNoSetter(); |
| testPatchNonFunction(); |
| |
| testPatchAndSelector(); |
| |
| testEffectiveTarget(); /// bug: ok |
| |
| testAnalyzeAllInjectedMembers(); |
| testTypecheckPatchedMembers(); |
| } |