mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
Add support for patching class methods.
R=brianwilkerson@google.com, paulberry@google.com BUG= Review URL: https://codereview.chromium.org/2412453007 .
This commit is contained in:
parent
6d66f3dea8
commit
cbb898bca7
|
@ -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<ClassMember> 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<CompilationUnitMember> 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);
|
||||
|
|
|
@ -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<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
|
|||
}, 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(
|
||||
|
|
Loading…
Reference in a new issue