blob: dc3a9034f6a31ddc9e16ec6739737932741a0eff [file] [log] [blame]
// 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.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/dart/sdk/patch.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/util/fast_uri.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(SdkPatcherTest);
});
}
@reflectiveTest
class SdkPatcherTest {
MemoryResourceProvider provider = new MemoryResourceProvider();
Folder sdkFolder;
FolderBasedDartSdk sdk;
SdkPatcher patcher = new SdkPatcher();
RecordingErrorListener listener = new RecordingErrorListener();
void setUp() {
sdkFolder = provider.getFolder(_p('/sdk'));
}
test_class_constructor_append_fail_notPrivate_named() {
expect(() {
_doTopLevelPatching(
r'''
class C {}
''',
r'''
@patch
class C {
C.named() {}
}
''');
}, throwsArgumentError);
}
test_class_constructor_append_fail_notPrivate_unnamed() {
expect(() {
_doTopLevelPatching(
r'''
class C {}
''',
r'''
@patch
class C {
C() {}
}
''');
}, throwsArgumentError);
}
test_class_constructor_append_named() {
CompilationUnit unit = _doTopLevelPatching(
r'''
class C {
}
''',
r'''
@patch
class C {
C._named() {}
}
''');
_assertUnitCode(unit, 'class C {C._named() {}}');
ClassDeclaration clazz = unit.declarations[0];
ConstructorDeclaration constructor = clazz.members[0];
_assertPrevNextToken(clazz.leftBracket, constructor.beginToken);
_assertPrevNextToken(constructor.endToken, clazz.rightBracket);
}
test_class_constructor_append_unnamed() {
CompilationUnit unit = _doTopLevelPatching(
r'''
class _C {
}
''',
r'''
@patch
class _C {
_C() {}
}
''');
_assertUnitCode(unit, 'class _C {_C() {}}');
ClassDeclaration clazz = unit.declarations[0];
ConstructorDeclaration constructor = clazz.members[0];
_assertPrevNextToken(clazz.leftBracket, constructor.beginToken);
_assertPrevNextToken(constructor.endToken, clazz.rightBracket);
}
test_class_constructor_patch() {
CompilationUnit unit = _doTopLevelPatching(
r'''
class C {
external C.named();
}
''',
r'''
@patch
class C {
@patch
C.named() {
print(42);
}
}
''');
_assertUnitCode(unit, 'class C {C.named() {print(42);}}');
ClassDeclaration clazz = unit.declarations[0];
ConstructorDeclaration constructor = clazz.members[0];
expect(constructor.externalKeyword, isNull);
_assertPrevNextToken(
constructor.parameters.endToken, constructor.body.beginToken);
_assertPrevNextToken(constructor.endToken, clazz.rightBracket);
}
test_class_constructor_patch_fail_baseFactory_patchGenerative() {
expect(() {
_doTopLevelPatching(
r'''
class C {
external factory C.named();
}
''',
r'''
@patch
class C {
@patch
C.named() {}
}
''');
}, throwsArgumentError);
}
test_class_constructor_patch_fail_baseGenerative_patchFactory() {
expect(() {
_doTopLevelPatching(
r'''
class C {
external C.named();
}
''',
r'''
@patch
class C {
@patch
factory C.named() {}
}
''');
}, throwsArgumentError);
}
test_class_constructor_patch_fail_hasInitializers() {
expect(() {
_doTopLevelPatching(
r'''
class C {
int f;
external C.named() : f = 1;
}
''',
r'''
@patch
class C {
@patch
C.named() : f = 2 {}
}
''');
}, throwsArgumentError);
}
test_class_constructor_patch_fail_noExternalKeyword() {
expect(() {
_doTopLevelPatching(
r'''
class C {
C.named();
}
''',
r'''
@patch
class C {
@patch
C.named() {}
}
''');
}, throwsArgumentError);
}
test_class_constructor_patch_initializers() {
CompilationUnit unit = _doTopLevelPatching(
r'''
class C {
int f;
external C.named();
}
''',
r'''
@patch
class C {
@patch
C.named() : f = 2 {
print(42);
}
}
''');
_assertUnitCode(unit, 'class C {int f; C.named() : f = 2 {print(42);}}');
ClassDeclaration clazz = unit.declarations[0];
ConstructorDeclaration constructor = clazz.members[1];
expect(constructor.externalKeyword, isNull);
_assertPrevNextToken(constructor.parameters.endToken,
constructor.initializers.beginToken.previous);
_assertPrevNextToken(constructor.endToken, clazz.rightBracket);
}
test_class_field_append() {
CompilationUnit unit = _doTopLevelPatching(
r'''
class C {
void a() {}
}
''',
r'''
@patch
class C {
int _b = 42;
}
''');
_assertUnitCode(unit, 'class C {void a() {} int _b = 42;}');
ClassDeclaration clazz = unit.declarations[0];
MethodDeclaration a = clazz.members[0];
FieldDeclaration b = clazz.members[1];
_assertPrevNextToken(a.endToken, b.beginToken);
_assertPrevNextToken(b.endToken, clazz.rightBracket);
}
test_class_field_append_fail_moreThanOne() {
expect(() {
_doTopLevelPatching(
r'''
class A {}
''',
r'''
@patch
class A {
@patch
int _f1, _f2;
}
''');
}, throwsArgumentError);
}
test_class_field_append_fail_notPrivate() {
expect(() {
_doTopLevelPatching(
r'''
class A {}
''',
r'''
@patch
class A {
@patch
int b;
}
''');
}, throwsArgumentError);
}
test_class_field_append_publicInPrivateClass() {
CompilationUnit unit = _doTopLevelPatching(
r'''
class _C {
void a() {}
}
''',
r'''
@patch
class _C {
int b = 42;
}
''');
_assertUnitCode(unit, 'class _C {void a() {} int b = 42;}');
ClassDeclaration clazz = unit.declarations[0];
MethodDeclaration a = clazz.members[0];
FieldDeclaration b = clazz.members[1];
_assertPrevNextToken(a.endToken, b.beginToken);
_assertPrevNextToken(b.endToken, clazz.rightBracket);
}
test_class_field_patch_fail() {
expect(() {
_doTopLevelPatching(
r'''
class A {}
''',
r'''
@patch
class A {
@patch
int _f;
}
''');
}, throwsArgumentError);
}
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(
r'''
import 'a.dart';
''',
r'''
export 'c.dart';
''');
}, throwsArgumentError);
}
test_directive_import() {
CompilationUnit unit = _doTopLevelPatching(
r'''
import 'a.dart';
part 'b.dart';
int bar() => 0;
''',
r'''
import 'c.dart';
''');
_assertUnitCode(unit,
"import 'a.dart'; part 'b.dart'; import 'c.dart'; int bar() => 0;");
}
test_fail_noSuchLibrary() {
expect(() {
_setSdkLibraries('const LIBRARIES = const {};');
_createSdk();
File file = provider.newFile(_p('/sdk/lib/test/test.dart'), '');
Source source = file.createSource(FastUri.parse('dart:test'));
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
}, throwsArgumentError);
}
test_fail_patchFileDoesNotExist() {
expect(() {
_setSdkLibraries(r'''
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
'test' : const LibraryInfo(
'test/test.dart',
patches: {VM_PLATFORM: ['does_not_exists.dart']}),
};''');
_createSdk();
File file = provider.newFile(_p('/sdk/lib/test/test.dart'), '');
Source source = file.createSource(FastUri.parse('dart:test'));
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
}, throwsArgumentError);
}
test_internal_allowNewPublicNames() {
_setSdkLibraries(r'''
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
'_internal' : const LibraryInfo(
'internal/internal.dart',
patches: {VM_PLATFORM: ['internal/internal_patch.dart']}),
};''');
File file = provider.newFile(
_p('/sdk/lib/internal/internal.dart'),
r'''
library dart._internal;
class A {}
class B {
B();
}
''');
provider.newFile(
_p('/sdk/lib/internal/internal_patch.dart'),
r'''
@patch
class B {
int newField;
B.newConstructor();
int newMethod() => 1;
}
class NewClass {}
int newFunction() => 2;
''');
_createSdk();
Source source = file.createSource(FastUri.parse('dart:_internal'));
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
_assertUnitCode(
unit,
'library dart._internal; class A {} '
'class B {B(); int newField; B.newConstructor(); int newMethod() => 1;} '
'class NewClass {} int newFunction() => 2;');
}
test_part() {
String baseLibCode = r'''
library test;
part 'test_part.dart';
class A {}
''';
String basePartCode = r'''
part of test;
class B {}
''';
_setSdkLibraries(r'''
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
'test' : const LibraryInfo(
'test/test.dart',
patches: {VM_PLATFORM: ['test/test_patch.dart']}),
};''');
File fileLib = provider.newFile(_p('/sdk/lib/test/test.dart'), baseLibCode);
File filePart =
provider.newFile(_p('/sdk/lib/test/test_part.dart'), basePartCode);
provider.newFile(
_p('/sdk/lib/test/test_patch.dart'),
r'''
import 'foo.dart';
@patch
class A {
int _a() => 1;
}
@patch
class B {
int _b() => 1;
}
class _C {}
''');
_createSdk();
{
Uri uri = FastUri.parse('dart:test');
Source source = fileLib.createSource(uri);
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
_assertUnitCode(
unit,
"library test; part 'test_part.dart'; import 'foo.dart'; "
"class A {int _a() => 1;} class _C {}");
}
{
Uri uri = FastUri.parse('dart:test/test_part.dart');
Source source = filePart.createSource(uri);
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
_assertUnitCode(unit, "part of test; class B {int _b() => 1;}");
}
}
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(
r'''
int foo() => 0;
''',
r'''
int _bar;
''');
}, throwsArgumentError);
}
test_topLevel_function_append() {
CompilationUnit unit = _doTopLevelPatching(
r'''
int foo() => 0;
''',
r'''
int _bar1() => 1;
int _bar2() => 2;
''');
_assertUnitCode(
unit, 'int foo() => 0; int _bar1() => 1; int _bar2() => 2;');
FunctionDeclaration foo = unit.declarations[0];
FunctionDeclaration bar1 = unit.declarations[1];
FunctionDeclaration bar2 = unit.declarations[2];
_assertPrevNextToken(foo.endToken, bar1.beginToken);
_assertPrevNextToken(bar1.endToken, bar2.beginToken);
}
test_topLevel_function_fail_noExternalKeyword() {
expect(() {
_doTopLevelPatching(
r'''
int foo();
''',
r'''
@patch
int foo() => 1;
''');
}, throwsArgumentError);
}
test_topLevel_function_fail_notPrivate() {
expect(() {
_doTopLevelPatching(
r'''
int foo() => 1;
''',
r'''
int bar() => 2;
''');
}, throwsArgumentError);
}
test_topLevel_functionTypeAlias_append() {
CompilationUnit unit = _doTopLevelPatching(
r'''
int foo() => 0;
''',
r'''
typedef int _bar1();
typedef int _bar2();
''');
_assertUnitCode(
unit, 'int foo() => 0; typedef int _bar1(); typedef int _bar2();');
FunctionDeclaration foo = unit.declarations[0];
FunctionTypeAlias bar1 = unit.declarations[1];
FunctionTypeAlias bar2 = unit.declarations[2];
_assertPrevNextToken(foo.endToken, bar1.beginToken);
_assertPrevNextToken(bar1.endToken, bar2.beginToken);
expect(unit.endToken.type, TokenType.EOF);
expect(bar2.endToken.next, same(unit.endToken));
}
test_topLevel_functionTypeAlias_fail_hasAnnotation() {
expect(() {
_doTopLevelPatching(
r'''
int foo() => 0;
''',
r'''
@patch
typedef int _bar();
''');
}, throwsArgumentError);
}
test_topLevel_functionTypeAlias_fail_notPrivate() {
expect(() {
_doTopLevelPatching(
r'''
int foo() => 0;
''',
r'''
typedef int bar();
''');
}, throwsArgumentError);
}
test_topLevel_patch_function() {
CompilationUnit unit = _doTopLevelPatching(
r'''
external int foo();
int bar() => 2;
''',
r'''
@patch
int foo() => 1;
''');
_assertUnitCode(unit, 'int foo() => 1; int bar() => 2;');
// Prepare functions.
FunctionDeclaration foo = unit.declarations[0];
FunctionDeclaration bar = unit.declarations[1];
// The "external" token is removed from the stream.
{
expect(foo.externalKeyword, isNull);
Token token = foo.beginToken;
expect(token.lexeme, 'int');
expect(token.previous.type, TokenType.EOF);
}
// The body tokens are included into the patched token stream.
{
FunctionExpression fooExpr = foo.functionExpression;
FunctionBody fooBody = fooExpr.body;
expect(fooBody.beginToken.previous, same(fooExpr.parameters.endToken));
expect(fooBody.endToken.next, same(bar.beginToken));
}
}
test_topLevel_patch_function_blockBody() {
CompilationUnit unit = _doTopLevelPatching(
r'''
external int foo();
''',
r'''
@patch
int foo() {int v = 1; return v + 2;}
''');
_assertUnitCode(unit, 'int foo() {int v = 1; return v + 2;}');
}
test_topLevel_patch_getter() {
CompilationUnit unit = _doTopLevelPatching(
r'''
external int get foo;
int bar() => 2;
''',
r'''
@patch
int get foo => 1;
''');
_assertUnitCode(unit, 'int get foo => 1; int bar() => 2;');
}
test_topLevel_patch_setter() {
CompilationUnit unit = _doTopLevelPatching(
r'''
external void set foo(int val);
int bar() => 2;
''',
r'''
@patch
void set foo(int val) {}
''');
_assertUnitCode(unit, 'void set foo(int val) {} int bar() => 2;');
}
void _assertUnitCode(CompilationUnit unit, String expectedCode) {
expect(unit.toSource(), expectedCode);
}
void _createSdk() {
sdk = new FolderBasedDartSdk(provider, sdkFolder);
sdk.analysisOptions = new AnalysisOptionsImpl()..strongMode = true;
}
CompilationUnit _doTopLevelPatching(String baseCode, String patchCode) {
_setSdkLibraries(r'''
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
'test' : const LibraryInfo(
'test/test.dart',
patches: {VM_PLATFORM: ['test/test_patch.dart']}),
};''');
File file = provider.newFile(_p('/sdk/lib/test/test.dart'), baseCode);
provider.newFile(_p('/sdk/lib/test/test_patch.dart'), patchCode);
_createSdk();
Source source = file.createSource(FastUri.parse('dart:test'));
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
return unit;
}
String _p(String path) => provider.convertPath(path);
void _setSdkLibraries(String code) {
provider.newFile(
_p('/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart'), code);
}
static void _assertPrevNextToken(Token prev, Token next) {
expect(prev.next, same(next));
expect(next.previous, same(prev));
}
}