From cbb898bca74b38859f1c0f2e49d1beec6fbd9509 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 13 Oct 2016 11:55:53 -0700 Subject: [PATCH] Add support for patching class methods. R=brianwilkerson@google.com, paulberry@google.com BUG= Review URL: https://codereview.chromium.org/2412453007 . --- pkg/analyzer/lib/src/dart/sdk/patch.dart | 102 +++++++++--- .../test/src/dart/sdk/patch_test.dart | 152 ++++++++++++++++++ 2 files changed, 229 insertions(+), 25 deletions(-) diff --git a/pkg/analyzer/lib/src/dart/sdk/patch.dart b/pkg/analyzer/lib/src/dart/sdk/patch.dart index 576cb96149b..f2bfeb30cb8 100644 --- a/pkg/analyzer/lib/src/dart/sdk/patch.dart +++ b/pkg/analyzer/lib/src/dart/sdk/patch.dart @@ -72,14 +72,13 @@ class SdkPatcher { _patchDirectives( source, unit, patchSource, patchUnit, addNewTopLevelDeclarations); - _patchTopLevelDeclarations( - source, unit, patchSource, patchUnit, addNewTopLevelDeclarations); + _patchTopLevelDeclarations(unit, patchUnit, addNewTopLevelDeclarations); } } - void _failExternalKeyword(Source source, String name, int offset) { + void _failExternalKeyword(String name, int offset) { throw new ArgumentError( - 'The keyword "external" was expected for "$name" in $source @ $offset.'); + 'The keyword "external" was expected for "$name" in $_baseDesc @ $offset.'); } void _failIfPublicName(AstNode node, String name) { @@ -99,6 +98,53 @@ class SdkPatcher { return 'the line ${location.lineNumber}'; } + void _patchClassMembers( + ClassDeclaration baseClass, ClassDeclaration patchClass) { + List membersToAppend = []; + for (ClassMember patchMember in patchClass.members) { + if (patchMember is MethodDeclaration) { + String name = patchMember.name.name; + if (_hasPatchAnnotation(patchMember.metadata)) { + for (ClassMember baseMember in baseClass.members) { + if (baseMember is MethodDeclaration && + baseMember.name.name == name) { + // Remove the "external" keyword. + Token externalKeyword = baseMember.externalKeyword; + if (externalKeyword != null) { + baseMember.externalKeyword = null; + _removeToken(externalKeyword); + } else { + _failExternalKeyword(name, baseMember.offset); + } + // Replace the body. + FunctionBody oldBody = baseMember.body; + FunctionBody newBody = patchMember.body; + _replaceNodeTokens(oldBody, newBody); + baseMember.body = newBody; + } + } + } else { + _failIfPublicName(patchMember, name); + membersToAppend.add(patchMember); + } + } else { + // TODO(scheglov) support field + // TODO(scheglov) support constructors + String className = patchClass.name.name; + _failInPatch('contains an unsupported class member in $className', + patchMember.offset); + } + } + // Append new top-level declarations. + Token lastToken = baseClass.endToken.previous; + for (ClassMember newMember in membersToAppend) { + newMember.endToken.setNext(lastToken.next); + lastToken.setNext(newMember.beginToken); + baseClass.members.add(newMember); + lastToken = newMember.endToken; + } + } + void _patchDirectives( Source baseSource, CompilationUnit baseUnit, @@ -115,12 +161,8 @@ class SdkPatcher { } } - void _patchTopLevelDeclarations( - Source baseSource, - CompilationUnit baseUnit, - Source patchSource, - CompilationUnit patchUnit, - bool addNewTopLevelDeclarations) { + void _patchTopLevelDeclarations(CompilationUnit baseUnit, + CompilationUnit patchUnit, bool addNewTopLevelDeclarations) { List declarationsToAppend = []; for (CompilationUnitMember patchDeclaration in patchUnit.declarations) { if (patchDeclaration is FunctionDeclaration) { @@ -130,22 +172,19 @@ class SdkPatcher { if (patchDeclaration is FunctionDeclaration && baseDeclaration is FunctionDeclaration && baseDeclaration.name.name == name) { - if (_hasPatchAnnotation(patchDeclaration.metadata)) { - // Remove the "external" keyword. - Token externalKeyword = baseDeclaration.externalKeyword; - if (externalKeyword != null) { - baseDeclaration.externalKeyword = null; - _removeToken(externalKeyword); - } else { - _failExternalKeyword( - baseSource, name, baseDeclaration.offset); - } - // Replace the body. - FunctionExpression oldExpr = baseDeclaration.functionExpression; - FunctionBody newBody = patchDeclaration.functionExpression.body; - _replaceNodeTokens(oldExpr.body, newBody); - oldExpr.body = newBody; + // Remove the "external" keyword. + Token externalKeyword = baseDeclaration.externalKeyword; + if (externalKeyword != null) { + baseDeclaration.externalKeyword = null; + _removeToken(externalKeyword); + } else { + _failExternalKeyword(name, baseDeclaration.offset); } + // Replace the body. + FunctionExpression oldExpr = baseDeclaration.functionExpression; + FunctionBody newBody = patchDeclaration.functionExpression.body; + _replaceNodeTokens(oldExpr.body, newBody); + oldExpr.body = newBody; } } } else if (addNewTopLevelDeclarations) { @@ -159,6 +198,19 @@ class SdkPatcher { } _failIfPublicName(patchDeclaration, patchDeclaration.name.name); declarationsToAppend.add(patchDeclaration); + } else if (patchDeclaration is ClassDeclaration) { + if (_hasPatchAnnotation(patchDeclaration.metadata)) { + String name = patchDeclaration.name.name; + for (CompilationUnitMember baseDeclaration in baseUnit.declarations) { + if (baseDeclaration is ClassDeclaration && + baseDeclaration.name.name == name) { + _patchClassMembers(baseDeclaration, patchDeclaration); + } + } + } else { + _failIfPublicName(patchDeclaration, patchDeclaration.name.name); + declarationsToAppend.add(patchDeclaration); + } } else { _failInPatch('contains an unsupported top-level declaration', patchDeclaration.offset); diff --git a/pkg/analyzer/test/src/dart/sdk/patch_test.dart b/pkg/analyzer/test/src/dart/sdk/patch_test.dart index b5ecd52f546..ad7127c76eb 100644 --- a/pkg/analyzer/test/src/dart/sdk/patch_test.dart +++ b/pkg/analyzer/test/src/dart/sdk/patch_test.dart @@ -34,6 +34,117 @@ class SdkPatcherTest { sdkFolder = provider.getFolder(_p('/sdk')); } + test_class_getter_append() { + CompilationUnit unit = _doTopLevelPatching( + r''' +class C { + void a() {} +} +''', + r''' +@patch +class C { + int get _b => 2; +} +'''); + _assertUnitCode(unit, 'class C {void a() {} int get _b => 2;}'); + } + + test_class_method_append() { + CompilationUnit unit = _doTopLevelPatching( + r''' +class C { + void a() {} +} +''', + r''' +@patch +class C { + void _b() {} + void _c() {} +} +'''); + _assertUnitCode(unit, 'class C {void a() {} void _b() {} void _c() {}}'); + ClassDeclaration clazz = unit.declarations[0]; + MethodDeclaration a = clazz.members[0]; + MethodDeclaration b = clazz.members[1]; + MethodDeclaration c = clazz.members[2]; + _assertPrevNextToken(a.endToken, b.beginToken); + _assertPrevNextToken(b.endToken, c.beginToken); + _assertPrevNextToken(c.endToken, clazz.rightBracket); + } + + test_class_method_fail_notPrivate() { + expect(() { + _doTopLevelPatching( + r''' +class A {} +''', + r''' +@patch +class A { + void m() {} +} +'''); + }, throwsArgumentError); + } + + test_class_method_patch() { + CompilationUnit unit = _doTopLevelPatching( + r''' +class C { + external int m(); +} +''', + r''' +@patch +class C { + @patch + int m() => 42; +} +'''); + _assertUnitCode(unit, 'class C {int m() => 42;}'); + ClassDeclaration clazz = unit.declarations[0]; + MethodDeclaration m = clazz.members[0]; + expect(m.externalKeyword, isNull); + _assertPrevNextToken(m.parameters.rightParenthesis, m.body.beginToken); + _assertPrevNextToken(m.body.endToken, clazz.rightBracket); + } + + test_class_method_patch_fail_noExternalKeyword() { + expect(() { + _doTopLevelPatching( + r''' +class C { + int m(); +} +''', + r''' +@patch +class C { + @patch + int m() => 42; +} +'''); + }, throwsArgumentError); + } + + test_class_setter_append() { + CompilationUnit unit = _doTopLevelPatching( + r''' +class C { + void a() {} +} +''', + r''' +@patch +class C { + void set _b(_) {} +} +'''); + _assertUnitCode(unit, 'class C {void a() {} void set _b(_) {}}'); + } + test_directive_fail_export() { expect(() { _doTopLevelPatching( @@ -87,6 +198,47 @@ final Map LIBRARIES = const { }, throwsArgumentError); } + test_topLevel_class_append() { + CompilationUnit unit = _doTopLevelPatching( + r''' +class A {} +''', + r''' +class _B { + void mmm() {} +} +'''); + _assertUnitCode(unit, 'class A {} class _B {void mmm() {}}'); + ClassDeclaration a = unit.declarations[0]; + ClassDeclaration b = unit.declarations[1]; + _assertPrevNextToken(a.endToken, b.beginToken); + } + + test_topLevel_class_fail_mixinApplication() { + expect(() { + _doTopLevelPatching( + r''' +class A {} +''', + r''' +class _B {} +class _C = Object with _B; +'''); + }, throwsArgumentError); + } + + test_topLevel_class_fail_notPrivate() { + expect(() { + _doTopLevelPatching( + r''' +class A {} +''', + r''' +class B {} +'''); + }, throwsArgumentError); + } + test_topLevel_fail_topLevelVariable() { expect(() { _doTopLevelPatching(