| // Copyright (c) 2014, 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 analyzer.test.generated.incremental_resolver_test; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/exception/exception.dart'; |
| import 'package:analyzer/src/context/cache.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/scanner/reader.dart'; |
| import 'package:analyzer/src/dart/scanner/scanner.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/incremental_logger.dart' as logging; |
| import 'package:analyzer/src/generated/incremental_resolution_validator.dart'; |
| import 'package:analyzer/src/generated/incremental_resolver.dart'; |
| import 'package:analyzer/src/generated/java_engine.dart'; |
| import 'package:analyzer/src/generated/parser.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/generated/testing/ast_test_factory.dart'; |
| import 'package:analyzer/src/generated/testing/element_factory.dart'; |
| import 'package:analyzer/src/generated/utilities_collection.dart'; |
| import 'package:analyzer/src/task/dart.dart'; |
| import 'package:analyzer/task/dart.dart'; |
| import 'package:analyzer/task/model.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'analysis_context_factory.dart'; |
| import 'resolver_test_case.dart'; |
| import 'test_support.dart'; |
| |
| main() { |
| initializeTestEnvironment(); |
| defineReflectiveSuite(() { |
| defineReflectiveTests(IncrementalResolverTest); |
| defineReflectiveTests(PoorMansIncrementalResolutionTest); |
| defineReflectiveTests(ResolutionContextBuilderTest); |
| }); |
| } |
| |
| void initializeTestEnvironment() {} |
| |
| void _assertEqualError(AnalysisError incError, AnalysisError fullError) { |
| if (incError.errorCode != fullError.errorCode || |
| incError.source != fullError.source || |
| incError.offset != fullError.offset || |
| incError.length != fullError.length || |
| incError.message != fullError.message) { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.writeln('Found error does not match expected error:'); |
| if (incError.errorCode == fullError.errorCode) { |
| buffer.write(' errorCode = '); |
| buffer.write(fullError.errorCode.uniqueName); |
| } else { |
| buffer.write(' Expected errorCode = '); |
| buffer.write(fullError.errorCode.uniqueName); |
| buffer.write(' found '); |
| buffer.write(incError.errorCode.uniqueName); |
| } |
| buffer.writeln(); |
| if (incError.source == fullError.source) { |
| buffer.write(' source = '); |
| buffer.write(fullError.source); |
| } else { |
| buffer.write(' Expected source = '); |
| buffer.write(fullError.source); |
| buffer.write(' found '); |
| buffer.write(incError.source); |
| } |
| buffer.writeln(); |
| if (incError.offset == fullError.offset) { |
| buffer.write(' offset = '); |
| buffer.write(fullError.offset); |
| } else { |
| buffer.write(' Expected offset = '); |
| buffer.write(fullError.offset); |
| buffer.write(' found '); |
| buffer.write(incError.offset); |
| } |
| buffer.writeln(); |
| if (incError.length == fullError.length) { |
| buffer.write(' length = '); |
| buffer.write(fullError.length); |
| } else { |
| buffer.write(' Expected length = '); |
| buffer.write(fullError.length); |
| buffer.write(' found '); |
| buffer.write(incError.length); |
| } |
| buffer.writeln(); |
| if (incError.message == fullError.message) { |
| buffer.write(' message = '); |
| buffer.write(fullError.message); |
| } else { |
| buffer.write(' Expected message = '); |
| buffer.write(fullError.message); |
| buffer.write(' found '); |
| buffer.write(incError.message); |
| } |
| fail(buffer.toString()); |
| } |
| } |
| |
| void _assertEqualErrors( |
| List<AnalysisError> incErrors, List<AnalysisError> fullErrors) { |
| expect(incErrors, hasLength(fullErrors.length)); |
| if (incErrors.isNotEmpty) { |
| incErrors.sort((a, b) => a.offset - b.offset); |
| } |
| if (fullErrors.isNotEmpty) { |
| fullErrors.sort((a, b) => a.offset - b.offset); |
| } |
| int length = incErrors.length; |
| for (int i = 0; i < length; i++) { |
| AnalysisError incError = incErrors[i]; |
| AnalysisError fullError = fullErrors[i]; |
| _assertEqualError(incError, fullError); |
| } |
| } |
| |
| void _checkCacheEntries(AnalysisCache cache) { |
| Set seen = new Set(); |
| MapIterator<AnalysisTarget, CacheEntry> it = cache.iterator(); |
| while (it.moveNext()) { |
| AnalysisTarget key = it.key; |
| if (cache.get(key) == null) { |
| fail("cache corrupted: value of $key changed to null"); |
| } |
| if (!seen.add(key)) { |
| fail("cache corrupted: $key appears more than once"); |
| } |
| } |
| } |
| |
| @reflectiveTest |
| class IncrementalResolverTest extends ResolverTestCase { |
| Source source; |
| String code; |
| LibraryElement library; |
| CompilationUnit unit; |
| |
| void setUp() { |
| super.setUp(); |
| logging.logger = logging.NULL_LOGGER; |
| } |
| |
| void test_classMemberAccessor_body() { |
| _resolveUnit(r''' |
| class A { |
| int get test { |
| return 1 + 2; |
| } |
| }'''); |
| _resolve(_editString('+', '*'), _isFunctionBody); |
| } |
| |
| void test_computeConstants_offsetChanged() { |
| _resolveUnit(r''' |
| int f() => 0; |
| main() { |
| const x1 = f(); |
| const x2 = f(); |
| const x3 = f(); |
| const x4 = f(); |
| const x5 = f(); |
| print(x1 + x2 + x3 + x4 + x5 + 1); |
| } |
| '''); |
| _resolve(_editString('x1', ' x1'), _isFunctionBody); |
| } |
| |
| void test_constructor_body() { |
| _resolveUnit(r''' |
| class A { |
| int f; |
| A(int a, int b) { |
| f = a + b; |
| } |
| }'''); |
| _resolve(_editString('+', '*'), _isFunctionBody); |
| } |
| |
| void test_constructor_label_add() { |
| _resolveUnit(r''' |
| class A { |
| A() { |
| return 42; |
| } |
| } |
| '''); |
| _resolve(_editString('return', 'label: return'), _isBlock); |
| } |
| |
| void test_constructor_localVariable_add() { |
| _resolveUnit(r''' |
| class A { |
| A() { |
| 42; |
| } |
| } |
| '''); |
| _resolve(_editString('42;', 'var res = 42;'), _isBlock); |
| } |
| |
| void test_function_localFunction_add() { |
| _resolveUnit(r''' |
| int main() { |
| return 0; |
| } |
| callIt(f) {} |
| '''); |
| _resolve(_editString('return 0;', 'callIt((p) {});'), _isBlock); |
| } |
| |
| void test_functionBody_body() { |
| _resolveUnit(r''' |
| main(int a, int b) { |
| return a + b; |
| }'''); |
| _resolve(_editString('+', '*'), _isFunctionBody); |
| } |
| |
| void test_functionBody_statement() { |
| _resolveUnit(r''' |
| main(int a, int b) { |
| return a + b; |
| }'''); |
| _resolve(_editString('+', '*'), _isStatement); |
| } |
| |
| void test_method_body() { |
| _resolveUnit(r''' |
| class A { |
| m(int a, int b) { |
| return a + b; |
| } |
| }'''); |
| _resolve(_editString('+', '*'), _isFunctionBody); |
| } |
| |
| void test_method_label_add() { |
| _resolveUnit(r''' |
| class A { |
| int m(int a, int b) { |
| return a + b; |
| } |
| } |
| '''); |
| _resolve(_editString('return', 'label: return'), _isBlock); |
| } |
| |
| void test_method_localFunction_add() { |
| _resolveUnit(r''' |
| class A { |
| int m() { |
| return 0; |
| } |
| } |
| callIt(f) {} |
| '''); |
| _resolve(_editString('return 0;', 'callIt((p) {});'), _isBlock); |
| } |
| |
| void test_method_localVariable_add() { |
| _resolveUnit(r''' |
| class A { |
| int m(int a, int b) { |
| return a + b; |
| } |
| } |
| '''); |
| _resolve( |
| _editString( |
| ' return a + b;', |
| r''' |
| int res = a + b; |
| return res; |
| '''), |
| _isBlock); |
| } |
| |
| void test_superInvocation() { |
| _resolveUnit(r''' |
| class A { |
| foo(p) {} |
| } |
| class B extends A { |
| bar() { |
| super.foo(1 + 2); |
| } |
| }'''); |
| _resolve(_editString('+', '*'), _isFunctionBody); |
| } |
| |
| void test_topLevelAccessor_body() { |
| _resolveUnit(r''' |
| int get test { |
| return 1 + 2; |
| }'''); |
| _resolve(_editString('+', '*'), _isFunctionBody); |
| } |
| |
| void test_topLevelFunction_label_add() { |
| _resolveUnit(r''' |
| int main(int a, int b) { |
| return a + b; |
| } |
| '''); |
| _resolve(_editString(' return', 'label: return a + b;'), _isBlock); |
| } |
| |
| void test_topLevelFunction_label_remove() { |
| _resolveUnit(r''' |
| int main(int a, int b) { |
| label: return a + b; |
| } |
| '''); |
| _resolve(_editString('label: ', ''), _isBlock); |
| } |
| |
| void test_topLevelFunction_localVariable_add() { |
| _resolveUnit(r''' |
| int main(int a, int b) { |
| return a + b; |
| } |
| '''); |
| _resolve( |
| _editString( |
| ' return a + b;', |
| r''' |
| int res = a + b; |
| return res; |
| '''), |
| _isBlock); |
| } |
| |
| void test_topLevelFunction_localVariable_remove() { |
| _resolveUnit(r''' |
| int main(int a, int b) { |
| int res = a * b; |
| return a + b; |
| } |
| '''); |
| _resolve(_editString('int res = a * b;', ''), _isBlock); |
| } |
| |
| void test_updateElementOffset() { |
| _resolveUnit(r''' |
| class A { |
| int am(String ap) { |
| int av = 1; |
| return av; |
| } |
| } |
| main(int a, int b) { |
| return a + b; |
| } |
| class B { |
| int bm(String bp) { |
| int bv = 1; |
| return bv; |
| } |
| } |
| '''); |
| _resolve(_editString('+', ' + '), _isStatement); |
| } |
| |
| _Edit _editString(String search, String replacement, [int length]) { |
| int offset = code.indexOf(search); |
| expect(offset, isNot(-1)); |
| if (length == null) { |
| length = search.length; |
| } |
| return new _Edit(offset, length, replacement); |
| } |
| |
| /** |
| * Applies [edit] to [code], find the [AstNode] specified by [predicate] |
| * and incrementally resolves it. |
| * |
| * Then resolves the new code from scratch and validates that results of |
| * the incremental resolution and non-incremental resolutions are the same. |
| */ |
| void _resolve(_Edit edit, Predicate<AstNode> predicate) { |
| int offset = edit.offset; |
| // parse "newCode" |
| String newCode = code.substring(0, offset) + |
| edit.replacement + |
| code.substring(offset + edit.length); |
| CompilationUnit newUnit = _parseUnit(newCode); |
| AnalysisCache cache = analysisContext2.analysisCache; |
| _checkCacheEntries(cache); |
| |
| // replace the node |
| AstNode oldNode = _findNodeAt(unit, offset, predicate); |
| AstNode newNode = _findNodeAt(newUnit, offset, predicate); |
| { |
| bool success = NodeReplacer.replace(oldNode, newNode); |
| expect(success, isTrue); |
| } |
| // update tokens |
| { |
| int delta = edit.replacement.length - edit.length; |
| _shiftTokens(unit.beginToken, offset, delta); |
| Token oldBeginToken = oldNode.beginToken; |
| Token oldEndTokenNext = oldNode.endToken.next; |
| oldBeginToken.previous.setNext(newNode.beginToken); |
| newNode.endToken.setNext(oldEndTokenNext); |
| } |
| // do incremental resolution |
| int updateOffset = edit.offset; |
| int updateEndOld = updateOffset + edit.length; |
| int updateOldNew = updateOffset + edit.replacement.length; |
| IncrementalResolver resolver; |
| LibrarySpecificUnit lsu = new LibrarySpecificUnit(source, source); |
| resolver = new IncrementalResolver(cache, cache.get(source), cache.get(lsu), |
| unit.element, updateOffset, updateEndOld, updateOldNew); |
| |
| BlockFunctionBody body = newNode.getAncestor((n) => n is BlockFunctionBody); |
| expect(body, isNotNull); |
| |
| resolver.resolve(body); |
| _checkCacheEntries(cache); |
| |
| List<AnalysisError> newErrors = analysisContext.computeErrors(source); |
| // resolve "newCode" from scratch |
| CompilationUnit fullNewUnit; |
| { |
| source = addSource(newCode); |
| _runTasks(); |
| LibraryElement library = resolve2(source); |
| fullNewUnit = resolveCompilationUnit(source, library); |
| } |
| _checkCacheEntries(cache); |
| |
| assertSameResolution(unit, fullNewUnit); |
| // errors |
| List<AnalysisError> newFullErrors = |
| analysisContext.getErrors(source).errors; |
| _assertEqualErrors(newErrors, newFullErrors); |
| // prepare for the next cycle |
| code = newCode; |
| } |
| |
| void _resolveUnit(String code) { |
| this.code = code; |
| source = addSource(code); |
| library = resolve2(source); |
| unit = resolveCompilationUnit(source, library); |
| _runTasks(); |
| _checkCacheEntries(analysisContext2.analysisCache); |
| } |
| |
| void _runTasks() { |
| AnalysisResult result = analysisContext.performAnalysisTask(); |
| while (result.changeNotices != null) { |
| result = analysisContext.performAnalysisTask(); |
| } |
| } |
| |
| static AstNode _findNodeAt( |
| CompilationUnit oldUnit, int offset, Predicate<AstNode> predicate) { |
| NodeLocator locator = new NodeLocator(offset); |
| AstNode node = locator.searchWithin(oldUnit); |
| return node.getAncestor(predicate); |
| } |
| |
| static bool _isBlock(AstNode node) => node is Block; |
| |
| static bool _isFunctionBody(AstNode node) => node is FunctionBody; |
| |
| static bool _isStatement(AstNode node) => node is Statement; |
| |
| static CompilationUnit _parseUnit(String code) { |
| var errorListener = new BooleanErrorListener(); |
| var reader = new CharSequenceReader(code); |
| var scanner = new Scanner(null, reader, errorListener); |
| var token = scanner.tokenize(); |
| var parser = new Parser(null, errorListener); |
| return parser.parseCompilationUnit(token); |
| } |
| |
| static void _shiftTokens(Token token, int afterOffset, int delta) { |
| while (true) { |
| if (token.offset > afterOffset) { |
| token.applyDelta(delta); |
| } |
| if (token.type == TokenType.EOF) { |
| break; |
| } |
| token = token.next; |
| } |
| } |
| } |
| |
| /** |
| * The test for [poorMansIncrementalResolution] function and its integration |
| * into [AnalysisContext]. |
| */ |
| @reflectiveTest |
| class PoorMansIncrementalResolutionTest extends ResolverTestCase { |
| final _TestLogger logger = new _TestLogger(); |
| |
| Source source; |
| String code; |
| LibraryElement oldLibrary; |
| CompilationUnit oldUnit; |
| CompilationUnitElement oldUnitElement; |
| |
| void assertSameReferencedNames( |
| ReferencedNames incNames, ReferencedNames fullNames) { |
| expectEqualSets(Iterable actual, Iterable expected) { |
| expect(actual, unorderedEquals(expected)); |
| } |
| |
| expectEqualSets(incNames.names, fullNames.names); |
| expectEqualSets(incNames.instantiatedNames, fullNames.instantiatedNames); |
| expectEqualSets(incNames.superToSubs.keys, fullNames.superToSubs.keys); |
| for (String key in fullNames.superToSubs.keys) { |
| expectEqualSets(incNames.superToSubs[key], fullNames.superToSubs[key]); |
| } |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| _resetWithIncremental(true); |
| } |
| |
| void test_computeConstants() { |
| _resolveUnit(r''' |
| int f() => 0; |
| main() { |
| const x = f(); |
| print(x + 1); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| int f() => 0; |
| main() { |
| const x = f(); |
| print(x + 2); |
| } |
| ''', |
| expectCachePostConstantsValid: false); |
| } |
| |
| void test_dartDoc_beforeField() { |
| _resolveUnit(r''' |
| class A { |
| /** |
| * A field [field] of type [int] in class [A]. |
| */ |
| int field; |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| /** |
| * A field [field] of the type [int] in the class [A]. |
| * Updated, with a reference to the [String] type. |
| */ |
| int field; |
| } |
| '''); |
| } |
| |
| void test_dartDoc_beforeTopLevelVariable() { |
| _resolveUnit(r''' |
| /** |
| * Variables [V1] and [V2] of type [int]. |
| */ |
| int V1, V2; |
| '''); |
| _updateAndValidate(r''' |
| /** |
| * Variables [V1] and [V2] of type [int]. |
| * Updated, with a reference to the [String] type. |
| */ |
| int V1, V2; |
| '''); |
| } |
| |
| void test_dartDoc_clumsy_addReference() { |
| _resolveUnit(r''' |
| /** |
| * aaa bbbb |
| */ |
| main() { |
| } |
| '''); |
| _updateAndValidate(r''' |
| /** |
| * aaa [main] bbbb |
| */ |
| main() { |
| } |
| '''); |
| } |
| |
| void test_dartDoc_clumsy_removeReference() { |
| _resolveUnit(r''' |
| /** |
| * aaa [main] bbbb |
| */ |
| main() { |
| } |
| '''); |
| _updateAndValidate(r''' |
| /** |
| * aaa bbbb |
| */ |
| main() { |
| } |
| '''); |
| } |
| |
| void test_dartDoc_clumsy_updateText_beforeKeywordToken() { |
| _resolveUnit(r''' |
| /** |
| * A comment with the [int] type reference. |
| */ |
| class A {} |
| '''); |
| _updateAndValidate(r''' |
| /** |
| * A comment with the [int] type reference. |
| * Plus reference to [A] itself. |
| */ |
| class A {} |
| '''); |
| } |
| |
| void test_dartDoc_clumsy_updateText_insert() { |
| _resolveUnit(r''' |
| /** |
| * A function [main] with a parameter [p] of type [int]. |
| */ |
| main(int p) { |
| unresolvedFunctionProblem(); |
| } |
| /** |
| * Other comment with [int] reference. |
| */ |
| foo() {} |
| '''); |
| _updateAndValidate(r''' |
| /** |
| * A function [main] with a parameter [p] of type [int]. |
| * Inserted text with [String] reference. |
| */ |
| main(int p) { |
| unresolvedFunctionProblem(); |
| } |
| /** |
| * Other comment with [int] reference. |
| */ |
| foo() {} |
| '''); |
| } |
| |
| void test_dartDoc_clumsy_updateText_remove() { |
| _resolveUnit(r''' |
| /** |
| * A function [main] with a parameter [p] of type [int]. |
| * Some text with [String] reference to remove. |
| */ |
| main(int p) { |
| } |
| /** |
| * Other comment with [int] reference. |
| */ |
| foo() {} |
| '''); |
| _updateAndValidate(r''' |
| /** |
| * A function [main] with a parameter [p] of type [int]. |
| */ |
| main(int p) { |
| } |
| /** |
| * Other comment with [int] reference. |
| */ |
| foo() {} |
| '''); |
| } |
| |
| void test_dartDoc_elegant_addReference() { |
| _resolveUnit(r''' |
| /// aaa bbb |
| main() { |
| return 1; |
| } |
| '''); |
| _updateAndValidate(r''' |
| /// aaa [main] bbb |
| /// ccc [int] ddd |
| main() { |
| return 1; |
| } |
| '''); |
| } |
| |
| void test_dartDoc_elegant_removeReference() { |
| _resolveUnit(r''' |
| /// aaa [main] bbb |
| /// ccc [int] ddd |
| main() { |
| return 1; |
| } |
| '''); |
| _updateAndValidate(r''' |
| /// aaa bbb |
| main() { |
| return 1; |
| } |
| '''); |
| } |
| |
| void test_dartDoc_elegant_updateText_insertToken() { |
| _resolveUnit(r''' |
| /// A |
| /// [int] |
| class Test { |
| } |
| '''); |
| _updateAndValidate(r''' |
| /// A |
| /// |
| /// [int] |
| class Test { |
| } |
| '''); |
| } |
| |
| void test_dartDoc_elegant_updateText_removeToken() { |
| _resolveUnit(r''' |
| /// A |
| /// |
| /// [int] |
| class Test { |
| } |
| '''); |
| _updateAndValidate(r''' |
| /// A |
| /// [int] |
| class Test { |
| } |
| '''); |
| } |
| |
| void test_endOfLineComment_add_beforeKeywordToken() { |
| _resolveUnit(r''' |
| main() { |
| var v = 42; |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| // some comment |
| var v = 42; |
| } |
| '''); |
| } |
| |
| void test_endOfLineComment_add_beforeStringToken() { |
| _resolveUnit(r''' |
| main() { |
| print(0); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| // some comment |
| print(0); |
| } |
| '''); |
| } |
| |
| void test_endOfLineComment_edit() { |
| _resolveUnit(r''' |
| main() { |
| // some comment |
| print(0); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| // edited comment text |
| print(0); |
| } |
| '''); |
| } |
| |
| void test_endOfLineComment_outBody_add() { |
| _resolveUnit(r''' |
| main() { |
| Object x; |
| x.foo(); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| // 000 |
| main() { |
| Object x; |
| x.foo(); |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_endOfLineComment_outBody_remove() { |
| _resolveUnit(r''' |
| // 000 |
| main() { |
| Object x; |
| x.foo(); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| main() { |
| Object x; |
| x.foo(); |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_endOfLineComment_outBody_update() { |
| _resolveUnit(r''' |
| // 000 |
| main() { |
| Object x; |
| x.foo(); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| // 10 |
| main() { |
| Object x; |
| x.foo(); |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_endOfLineComment_remove() { |
| _resolveUnit(r''' |
| main() { |
| // some comment |
| print(0); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| print(0); |
| } |
| '''); |
| } |
| |
| void test_endOfLineComment_toDartDoc() { |
| _resolveUnit(r''' |
| class A { |
| // text |
| main() { |
| print(42); |
| } |
| }'''); |
| _updateAndValidate( |
| r''' |
| class A { |
| /// text |
| main() { |
| print(42); |
| } |
| }''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_constConstructor_initializer() { |
| _resolveUnit(r''' |
| class C { |
| final int x; |
| const C(this.x); |
| const C.foo() : x = 0; |
| } |
| main() { |
| const {const C(0): 0, const C.foo(): 1}; |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class C { |
| final int x; |
| const C(this.x); |
| const C.foo() : x = 1; |
| } |
| main() { |
| const {const C(0): 0, const C.foo(): 1}; |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_constructor_initializer_damage() { |
| _resolveUnit(r''' |
| class Problem { |
| final Map location; |
| final String message; |
| |
| Problem(Map json) |
| : location = json["location"], |
| message = json["message"]; |
| }'''); |
| _updateAndValidate( |
| r''' |
| class Problem { |
| final Map location; |
| final String message; |
| |
| Problem(Map json) |
| : location = json["location], |
| message = json["message"]; |
| }''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_constructor_initializer_remove() { |
| _resolveUnit(r''' |
| class Problem { |
| final String severity; |
| final Map location; |
| final String message; |
| |
| Problem(Map json) |
| : severity = json["severity"], |
| location = json["location"], |
| message = json["message"]; |
| }'''); |
| _updateAndValidate( |
| r''' |
| class Problem { |
| final String severity; |
| final Map location; |
| final String message; |
| |
| Problem(Map json) |
| : severity = json["severity"], |
| message = json["message"]; |
| }''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_endOfLineComment_localFunction_inTopLevelVariable() { |
| _resolveUnit(r''' |
| typedef int Binary(one, two, three); |
| |
| int Global = f((a, b, c) { |
| return 0; // Some comment |
| }); |
| '''); |
| _updateAndValidate( |
| r''' |
| typedef int Binary(one, two, three); |
| |
| int Global = f((a, b, c) { |
| return 0; // Some comment |
| }); |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_expressionBody() { |
| _resolveUnit(r''' |
| class A { |
| final f = (() => 1)(); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class A { |
| final f = (() => 2)(); |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_expressionBody2() { |
| _resolveUnit(r''' |
| class A { |
| int m() => 10 * 10; |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class A { |
| int m() => 10 * 100; |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_inBody_addAsync() { |
| _resolveUnit(r''' |
| class C { |
| test() {} |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class C { |
| test() async {} |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_inBody_async_addStar() { |
| _resolveUnit(r''' |
| import 'dart:async'; |
| class C { |
| Stream test() async {} |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| import 'dart:async'; |
| class C { |
| Stream test() async* {} |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_inBody_async_removeStar() { |
| _resolveUnit(r''' |
| import 'dart:async'; |
| class C { |
| Stream test() async* {} |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| import 'dart:async'; |
| class C { |
| Stream test() async {} |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_inBody_functionExpression() { |
| _resolveUnit(r''' |
| class C extends D { |
| static final f = () { |
| var x = 0; |
| }(); |
| } |
| |
| class D {} |
| '''); |
| _updateAndValidate( |
| r''' |
| class C extends D { |
| static final f = () { |
| var x = 01; |
| }(); |
| } |
| |
| class D {} |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_inBody_removeAsync() { |
| _resolveUnit(r''' |
| class C { |
| test() async {} |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class C { |
| test() {} |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_inBody_sync_addStar() { |
| _resolveUnit(r''' |
| class C { |
| test() {} |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class C { |
| test() sync* {} |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_topLevelFunction_name() { |
| _resolveUnit(r''' |
| a() {} |
| b() {} |
| '''); |
| _updateAndValidate( |
| r''' |
| a() {} |
| bb() {} |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_unbalancedCurlyBrackets_inNew() { |
| _resolveUnit(r''' |
| class A { |
| aaa() { |
| if (true) { |
| 1; |
| } |
| } |
| |
| bbb() { |
| print(0123456789); |
| } |
| }'''); |
| _updateAndValidate( |
| r''' |
| class A { |
| aaa() { |
| 1; |
| } |
| } |
| |
| bbb() { |
| print(0123456789); |
| } |
| }''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_unbalancedCurlyBrackets_inOld() { |
| _resolveUnit(r''' |
| class A { |
| aaa() { |
| 1; |
| } |
| } |
| |
| bbb() { |
| print(0123456789); |
| } |
| }'''); |
| _updateAndValidate( |
| r''' |
| class A { |
| aaa() { |
| if (true) { |
| 1; |
| } |
| } |
| |
| bbb() { |
| print(0123456789); |
| } |
| }''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_wholeConstructor() { |
| _resolveUnit(r''' |
| class A { |
| A(int a) { |
| print(a); |
| } |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class A { |
| A(int b) { |
| print(b); |
| } |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_wholeConstructor_addInitializer() { |
| _resolveUnit(r''' |
| class A { |
| int field; |
| A(); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class A { |
| int field; |
| A() : field = 5; |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_wholeFunction() { |
| _resolveUnit(r''' |
| foo() {} |
| main(int a) { |
| print(a); |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| foo() {} |
| main(int b) { |
| print(b); |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_false_wholeMethod() { |
| _resolveUnit(r''' |
| class A { |
| main(int a) { |
| print(a); |
| } |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| class A { |
| main(int b) { |
| print(b); |
| } |
| } |
| ''', |
| expectedSuccess: false); |
| } |
| |
| void test_fieldClassField_propagatedType() { |
| _resolveUnit(r''' |
| class A { |
| static const A b = const B(); |
| const A(); |
| } |
| |
| class B extends A { |
| const B(); |
| } |
| |
| main() { |
| print(12); |
| A.b; |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| static const A b = const B(); |
| const A(); |
| } |
| |
| class B extends A { |
| const B(); |
| } |
| |
| main() { |
| print(123); |
| A.b; |
| } |
| '''); |
| } |
| |
| void test_hasElementAfter_defaultParameter() { |
| _resolveUnit(r''' |
| main() { |
| print(1); |
| } |
| otherFunction([p = 0]) {} |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| print(2); |
| } |
| otherFunction([p = 0]) {} |
| '''); |
| } |
| |
| void test_inBody_expression() { |
| _resolveUnit(r''' |
| class A { |
| m() { |
| print(1); |
| } |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| m() { |
| print(2 + 3); |
| } |
| } |
| '''); |
| } |
| |
| void test_inBody_insertStatement() { |
| _resolveUnit(r''' |
| main() { |
| print(1); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| print(0); |
| print(1); |
| } |
| '''); |
| } |
| |
| void test_inBody_tokenToNode() { |
| _resolveUnit(r''' |
| main() { |
| var v = 42; |
| print(v); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| int v = 42; |
| print(v); |
| } |
| '''); |
| } |
| |
| void test_multiple_emptyLine() { |
| _resolveUnit(r''' |
| class A { |
| m() { |
| return true; |
| } |
| }'''); |
| for (int i = 0; i < 6; i++) { |
| if (i.isEven) { |
| _updateAndValidate( |
| r''' |
| class A { |
| m() { |
| return true; |
| |
| } |
| }''', |
| compareWithFull: false); |
| } else { |
| _updateAndValidate( |
| r''' |
| class A { |
| m() { |
| return true; |
| } |
| }''', |
| compareWithFull: false); |
| } |
| } |
| } |
| |
| void test_multiple_expression() { |
| _resolveUnit(r''' |
| main() { |
| print(1); |
| }'''); |
| for (int i = 0; i < 6; i++) { |
| if (i.isEven) { |
| _updateAndValidate( |
| r''' |
| main() { |
| print(12); |
| }''', |
| compareWithFull: false); |
| } else { |
| _updateAndValidate( |
| r''' |
| main() { |
| print(1); |
| }''', |
| compareWithFull: false); |
| } |
| } |
| } |
| |
| void test_strongMode_typeComments_insertWhitespace() { |
| _resolveUnit(r''' |
| import 'dart:async'; |
| |
| void fadeIn(int milliseconds) { |
| Future<String> f; |
| f.then/*<String>*/((e) {print("hello");}); |
| } |
| '''); |
| _updateAndValidate(r''' |
| import 'dart:async'; |
| |
| void fadeIn(int milliseconds) { |
| Future<String> f; |
| f.then/*<String>*/((e) {print("hello") ;}); |
| } |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenClassMembers_insert() { |
| _resolveUnit(r''' |
| class A { |
| a() {} |
| b() {} |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| a() {} |
| |
| b() {} |
| } |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenClassMembers_insert_beforeComment() { |
| _resolveUnit(r''' |
| class A { |
| a() {} |
| /// BBB |
| b() {} |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| a() {} |
| |
| /// BBB |
| b() {} |
| } |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenClassMembers_remove() { |
| _resolveUnit(r''' |
| class A { |
| a() {} |
| |
| b() {} |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| a() {} |
| b() {} |
| } |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenClassMembers_remove_beforeComment() { |
| _resolveUnit(r''' |
| class A { |
| a() {} |
| |
| /// BBB |
| b() {} |
| } |
| '''); |
| _updateAndValidate(r''' |
| class A { |
| a() {} |
| /// BBB |
| b() {} |
| } |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenUnitMembers_insert() { |
| _resolveUnit(r''' |
| a() {} |
| b() {} |
| '''); |
| _updateAndValidate(r''' |
| a() {} |
| |
| b() {} |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenUnitMembers_insert_beforeComment() { |
| _resolveUnit(r''' |
| a() {} |
| |
| // BBB |
| b() {} |
| '''); |
| _updateAndValidate(r''' |
| a() {} |
| |
| |
| // BBB |
| b() {} |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenUnitMembers_remove() { |
| _resolveUnit(r''' |
| a() { |
| print(1) |
| } |
| |
| b() { |
| foo(42); |
| } |
| foo(String p) {} |
| '''); |
| _updateAndValidate(r''' |
| a() { |
| print(1) |
| } |
| b() { |
| foo(42); |
| } |
| foo(String p) {} |
| '''); |
| } |
| |
| void test_true_emptyLine_betweenUnitMembers_remove_beforeComment() { |
| _resolveUnit(r''' |
| a() {} |
| |
| // BBB |
| b() {} |
| '''); |
| _updateAndValidate(r''' |
| a() {} |
| // BBB |
| b() {} |
| '''); |
| } |
| |
| void test_true_todoHint() { |
| _resolveUnit(r''' |
| main() { |
| print(1); |
| } |
| foo() { |
| // TODO |
| } |
| '''); |
| List<AnalysisError> oldErrors = analysisContext.computeErrors(source); |
| _updateAndValidate(r''' |
| main() { |
| print(2); |
| } |
| foo() { |
| // TODO |
| } |
| '''); |
| List<AnalysisError> newErrors = analysisContext.computeErrors(source); |
| _assertEqualErrors(newErrors, oldErrors); |
| } |
| |
| void test_unusedHint_add_wasUsedOnlyInPart() { |
| Source partSource = addNamedSource( |
| '/my_unit.dart', |
| r''' |
| part of lib; |
| |
| f(A a) { |
| a._foo(); |
| } |
| '''); |
| _resolveUnit(r''' |
| library lib; |
| part 'my_unit.dart'; |
| class A { |
| _foo() { |
| print(1); |
| } |
| } |
| '''); |
| _runTasks(); |
| // perform incremental resolution |
| _resetWithIncremental(true); |
| analysisContext2.setContents( |
| partSource, |
| r''' |
| part of lib; |
| |
| f(A a) { |
| // a._foo(); |
| } |
| '''); |
| // no hints right now, because we delay hints computing |
| { |
| List<AnalysisError> errors = analysisContext.getErrors(source).errors; |
| expect(errors, isEmpty); |
| } |
| // a new hint should be added |
| List<AnalysisError> errors = analysisContext.computeErrors(source); |
| expect(errors, hasLength(1)); |
| expect(errors[0].errorCode.type, ErrorType.HINT); |
| // the same hint should be reported using a ChangeNotice |
| bool noticeFound = false; |
| AnalysisResult result = analysisContext2.performAnalysisTask(); |
| for (ChangeNotice notice in result.changeNotices) { |
| if (notice.source == source) { |
| expect(notice.errors, contains(errors[0])); |
| noticeFound = true; |
| } |
| } |
| expect(noticeFound, isTrue); |
| } |
| |
| void test_unusedHint_false_stillUsedInPart() { |
| addNamedSource( |
| '/my_unit.dart', |
| r''' |
| part of lib; |
| |
| f(A a) { |
| a._foo(); |
| } |
| '''); |
| _resolveUnit(r''' |
| library lib; |
| part 'my_unit.dart'; |
| class A { |
| _foo() { |
| print(1); |
| } |
| } |
| '''); |
| // perform incremental resolution |
| _resetWithIncremental(true); |
| analysisContext2.setContents( |
| source, |
| r''' |
| library lib; |
| part 'my_unit.dart'; |
| class A { |
| _foo() { |
| print(12); |
| } |
| } |
| '''); |
| // no hints |
| List<AnalysisError> errors = analysisContext.getErrors(source).errors; |
| expect(errors, isEmpty); |
| } |
| |
| void test_updateConstantInitializer() { |
| _resolveUnit(r''' |
| main() { |
| const v = const [Unknown]; |
| } |
| '''); |
| _updateAndValidate( |
| r''' |
| main() { |
| const v = const [Unknown]; |
| } |
| ''', |
| expectCachePostConstantsValid: false); |
| } |
| |
| void test_updateErrors_addNew_hint1() { |
| _resolveUnit(r''' |
| int main() { |
| return 42; |
| } |
| '''); |
| _updateAndValidate(r''' |
| int main() { |
| } |
| '''); |
| } |
| |
| void test_updateErrors_addNew_hint2() { |
| _resolveUnit(r''' |
| main() { |
| int v = 0; |
| print(v); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| int v = 0; |
| } |
| '''); |
| } |
| |
| void test_updateErrors_addNew_parse() { |
| _resolveUnit(r''' |
| main() { |
| print(42); |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| print(42) |
| } |
| '''); |
| } |
| |
| void test_updateErrors_addNew_resolve() { |
| _resolveUnit(r''' |
| main() { |
| foo(); |
| } |
| foo() {} |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| bar(); |
| } |
| foo() {} |
| '''); |
| } |
| |
| void test_updateErrors_addNew_resolve2() { |
| _resolveUnit(r''' |
| // this comment is important to reproduce the problem |
| main() { |
| int vvv = 42; |
| print(vvv); |
| } |
| '''); |
| _updateAndValidate(r''' |
| // this comment is important to reproduce the problem |
| main() { |
| int vvv = 42; |
| print(vvv2); |
| } |
| '''); |
| } |
| |
| void test_updateErrors_addNew_scan() { |
| _resolveUnit(r''' |
| main() { |
| 1; |
| } |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| 1e; |
| } |
| '''); |
| } |
| |
| void test_updateErrors_addNew_verify() { |
| _resolveUnit(r''' |
| main() { |
| foo(0); |
| } |
| foo(int p) {} |
| '''); |
| _updateAndValidate(r''' |
| main() { |
| foo('abc'); |
| } |
| foo(int p) {} |
| '''); |
| } |
| |
| void test_updateErrors_invalidVerifyErrors() { |
| _resolveUnit(r''' |
| main() { |
| foo('aaa'); |
| } |
| main2() { |
| foo('bbb'); |
| } |
| foo(int p) {} |
| '''); |
| // Complete analysis, e.g. compute VERIFY_ERRORS. |
| _runTasks(); |
| // Invalidate VERIFY_ERRORS. |
| AnalysisCache cache = analysisContext2.analysisCache; |
| LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); |
| CacheEntry cacheEntry = cache.get(target); |
| expect(cacheEntry.getValue(VERIFY_ERRORS), hasLength(2)); |
| cacheEntry.setState(VERIFY_ERRORS, CacheState.INVALID); |
| // Perform incremental resolution. |
| _resetWithIncremental(true); |
| analysisContext2.setContents( |
| source, |
| r''' |
| main() { |
| foo(0); |
| } |
| main2() { |
| foo('bbb'); |
| } |
| foo(int p) {} |
| '''); |
| // VERIFY_ERRORS is still invalid. |
| expect(cacheEntry.getState(VERIFY_ERRORS), CacheState.INVALID); |
| // Continue analysis - run tasks, so recompute VERIFY_ERRORS. |
| _runTasks(); |
| expect(cacheEntry.getState(VERIFY_ERRORS), CacheState.VALID); |
| expect(cacheEntry.getValue(VERIFY_ERRORS), hasLength(1)); |
| } |
| |
| void test_updateErrors_removeExisting_hint() { |
| _resolveUnit(r''' |
| int main() { |
| } |
| '''); |
| _updateAndValidate(r''' |
| int main() { |
| return 42; |
| } |
| '''); |
| } |
| |
| void test_updateErrors_removeExisting_verify() { |
| _resolveUnit(r''' |
| f1() { |
| print(1) |
| } |
| f2() { |
| print(22) |
| } |
| f3() { |
| print(333) |
| } |
| '''); |
| _updateAndValidate(r''' |
| f1() { |
| print(1) |
| } |
| f2() { |
| print(22); |
| } |
| f3() { |
| print(333) |
| } |
| '''); |
| } |
| |
| void test_updateErrors_shiftExisting() { |
| _resolveUnit(r''' |
| f1() { |
| print(1) |
| } |
| f2() { |
| print(2); |
| } |
| f3() { |
| print(333) |
| } |
| '''); |
| _updateAndValidate(r''' |
| f1() { |
| print(1) |
| } |
| f2() { |
| print(22); |
| } |
| f3() { |
| print(333) |
| } |
| '''); |
| } |
| |
| void test_updateFunctionToForLoop() { |
| _resolveUnit(r''' |
| class PlayDrag { |
| final List<num> times = new List<num>(); |
| |
| PlayDrag.start() {} |
| |
| void update(num pos) { |
| fo (int i = times.length - 2; i >= 0; i--) {} |
| } |
| } |
| '''); |
| |
| _updateAndValidate( |
| r''' |
| class PlayDrag { |
| final List<num> times = new List<num>(); |
| |
| PlayDrag.start() {} |
| |
| void update(num pos) { |
| for (int i = times.length - 2; i >= 0; i--) {} |
| } |
| } |
| ''', |
| expectLibraryUnchanged: false); |
| } |
| |
| void test_visibleRange() { |
| _resolveUnit(r''' |
| class Test { |
| method1(p1) { |
| var v1; |
| f1() {} |
| return 1; |
| } |
| method2(p2) { |
| var v2; |
| f2() {} |
| return 2; |
| } |
| method3(p3) { |
| var v3; |
| f3() {} |
| return 3; |
| } |
| } |
| '''); |
| _updateAndValidate(r''' |
| class Test { |
| method1(p1) { |
| var v1; |
| f1() {} |
| return 1; |
| } |
| method2(p2) { |
| var v2; |
| f2() {} |
| return 2222; |
| } |
| method3(p3) { |
| var v3; |
| f3() {} |
| return 3; |
| } |
| } |
| '''); |
| } |
| |
| void test_whitespace_getElementAt() { |
| _resolveUnit(r''' |
| class A {} |
| class B extends A {} |
| '''); |
| { |
| ClassElement typeA = oldUnitElement.getType('A'); |
| expect(oldUnitElement.getElementAt(typeA.nameOffset), typeA); |
| } |
| { |
| ClassElement typeB = oldUnitElement.getType('B'); |
| expect(oldUnitElement.getElementAt(typeB.nameOffset), typeB); |
| } |
| _updateAndValidate(r''' |
| class A {} |
| |
| class B extends A {} |
| '''); |
| // getElementAt() caches results, it should be notified when offset |
| // are changed. |
| { |
| ClassElement typeA = oldUnitElement.getType('A'); |
| expect(oldUnitElement.getElementAt(typeA.nameOffset), typeA); |
| } |
| { |
| ClassElement typeB = oldUnitElement.getType('B'); |
| expect(oldUnitElement.getElementAt(typeB.nameOffset), typeB); |
| } |
| } |
| |
| void _assertCacheResults( |
| {bool expectLibraryUnchanged: true, |
| bool expectCachePostConstantsValid: true}) { |
| _assertCacheSourceResult(TOKEN_STREAM); |
| _assertCacheSourceResult(SCAN_ERRORS); |
| _assertCacheSourceResult(PARSED_UNIT); |
| _assertCacheSourceResult(PARSE_ERRORS); |
| if (!expectLibraryUnchanged) { |
| return; |
| } |
| _assertCacheSourceResult(LIBRARY_ELEMENT1); |
| _assertCacheSourceResult(LIBRARY_ELEMENT2); |
| _assertCacheSourceResult(LIBRARY_ELEMENT3); |
| _assertCacheSourceResult(LIBRARY_ELEMENT4); |
| _assertCacheSourceResult(LIBRARY_ELEMENT5); |
| _assertCacheSourceResult(LIBRARY_ELEMENT6); |
| _assertCacheSourceResult(LIBRARY_ELEMENT7); |
| _assertCacheSourceResult(LIBRARY_ELEMENT8); |
| _assertCacheSourceResult(LIBRARY_ELEMENT9); |
| if (expectCachePostConstantsValid) { |
| _assertCacheSourceResult(LIBRARY_ELEMENT); |
| } |
| _assertCacheUnitResult(RESOLVED_UNIT1); |
| _assertCacheUnitResult(RESOLVED_UNIT2); |
| _assertCacheUnitResult(RESOLVED_UNIT3); |
| _assertCacheUnitResult(RESOLVED_UNIT4); |
| _assertCacheUnitResult(RESOLVED_UNIT5); |
| _assertCacheUnitResult(RESOLVED_UNIT6); |
| _assertCacheUnitResult(RESOLVED_UNIT7); |
| _assertCacheUnitResult(RESOLVED_UNIT8); |
| _assertCacheUnitResult(RESOLVED_UNIT9); |
| _assertCacheUnitResult(RESOLVED_UNIT10); |
| _assertCacheUnitResult(RESOLVED_UNIT11); |
| if (expectCachePostConstantsValid) { |
| _assertCacheUnitResult(RESOLVED_UNIT12); |
| _assertCacheUnitResult(RESOLVED_UNIT); |
| } |
| } |
| |
| /** |
| * Assert that the [result] of [source] is not INVALID. |
| */ |
| void _assertCacheSourceResult(ResultDescriptor result) { |
| AnalysisCache cache = analysisContext2.analysisCache; |
| CacheState state = cache.getState(source, result); |
| expect(state, isNot(CacheState.INVALID), reason: result.toString()); |
| } |
| |
| /** |
| * Assert that the [result] of the defining unit [source] is not INVALID. |
| */ |
| void _assertCacheUnitResult(ResultDescriptor result) { |
| AnalysisCache cache = analysisContext2.analysisCache; |
| LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); |
| CacheState state = cache.getState(target, result); |
| expect(state, isNot(CacheState.INVALID), reason: result.toString()); |
| } |
| |
| void _assertEqualLineInfo(LineInfo incLineInfo, LineInfo fullLineInfo) { |
| for (int offset = 0; offset < 1000; offset++) { |
| LineInfo_Location incLocation = incLineInfo.getLocation(offset); |
| LineInfo_Location fullLocation = fullLineInfo.getLocation(offset); |
| if (incLocation.lineNumber != fullLocation.lineNumber || |
| incLocation.columnNumber != fullLocation.columnNumber) { |
| fail('At offset $offset ' + |
| '(${incLocation.lineNumber}, ${incLocation.columnNumber})' + |
| ' != ' + |
| '(${fullLocation.lineNumber}, ${fullLocation.columnNumber})'); |
| } |
| } |
| } |
| |
| /** |
| * Reset the analysis context to have the 'incremental' option set to the |
| * given value. |
| */ |
| void _resetWithIncremental(bool enable) { |
| AnalysisOptionsImpl analysisOptions = new AnalysisOptionsImpl(); |
| analysisOptions.strongMode = true; |
| analysisOptions.incremental = enable; |
| analysisOptions.incrementalApi = enable; |
| logging.logger = logger; |
| analysisContext2.analysisOptions = analysisOptions; |
| } |
| |
| void _resolveUnit(String code) { |
| this.code = code; |
| source = addSource(code); |
| oldLibrary = resolve2(source); |
| oldUnit = resolveCompilationUnit(source, oldLibrary); |
| oldUnitElement = oldUnit.element; |
| } |
| |
| void _runTasks() { |
| AnalysisResult result = analysisContext.performAnalysisTask(); |
| while (result.changeNotices != null) { |
| result = analysisContext.performAnalysisTask(); |
| } |
| } |
| |
| void _updateAndValidate(String newCode, |
| {bool expectedSuccess: true, |
| bool expectLibraryUnchanged: true, |
| bool expectCachePostConstantsValid: true, |
| bool compareWithFull: true, |
| bool runTasksBeforeIncremental: true}) { |
| // Run any pending tasks tasks. |
| if (runTasksBeforeIncremental) { |
| _runTasks(); |
| } |
| // Update the source - currently this may cause incremental resolution. |
| // Then request the updated resolved unit. |
| _resetWithIncremental(true); |
| analysisContext2.setContents(source, newCode); |
| CompilationUnit newUnit = resolveCompilationUnit(source, oldLibrary); |
| logger.expectNoErrors(); |
| List<AnalysisError> newErrors = analysisContext.computeErrors(source); |
| LineInfo newLineInfo = analysisContext.getLineInfo(source); |
| ReferencedNames newReferencedNames = |
| analysisContext.getResult(source, REFERENCED_NAMES); |
| // check for expected failure |
| if (!expectedSuccess) { |
| expect(newUnit.element, isNot(same(oldUnitElement))); |
| return; |
| } |
| // The cache must still have enough results to make the incremental |
| // resolution useful. |
| _assertCacheResults( |
| expectLibraryUnchanged: expectLibraryUnchanged, |
| expectCachePostConstantsValid: expectCachePostConstantsValid); |
| // The existing CompilationUnit[Element] should be updated. |
| expect(newUnit, same(oldUnit)); |
| expect(newUnit.element, same(oldUnitElement)); |
| expect(analysisContext.getResolvedCompilationUnit(source, oldLibrary), |
| same(oldUnit)); |
| // The only expected pending task should return the same resolved |
| // "newUnit", so all clients will get it using the usual way. |
| AnalysisResult analysisResult = analysisContext.performAnalysisTask(); |
| ChangeNotice notice = analysisResult.changeNotices[0]; |
| expect(notice.resolvedDartUnit, same(newUnit)); |
| // Resolve "newCode" from scratch. |
| if (compareWithFull) { |
| _resetWithIncremental(false); |
| changeSource(source, ''); |
| changeSource(source, newCode); |
| _runTasks(); |
| LibraryElement library = resolve2(source); |
| CompilationUnit fullNewUnit = resolveCompilationUnit(source, library); |
| // Validate tokens. |
| _assertEqualTokens(newUnit, fullNewUnit); |
| // Validate LineInfo |
| _assertEqualLineInfo(newLineInfo, analysisContext.getLineInfo(source)); |
| // Validate referenced names. |
| ReferencedNames fullReferencedNames = |
| analysisContext.getResult(source, REFERENCED_NAMES); |
| assertSameReferencedNames(newReferencedNames, fullReferencedNames); |
| // Validate that "incremental" and "full" units have the same resolution. |
| try { |
| assertSameResolution(newUnit, fullNewUnit, validateTypes: true); |
| } on IncrementalResolutionMismatch catch (mismatch) { |
| fail(mismatch.message); |
| } |
| List<AnalysisError> newFullErrors = |
| analysisContext.getErrors(source).errors; |
| _assertEqualErrors(newErrors, newFullErrors); |
| } |
| _checkCacheEntries(analysisContext2.analysisCache); |
| } |
| |
| static void _assertEqualToken(Token incToken, Token fullToken) { |
| // print('[${incToken.offset}] |$incToken| vs. [${fullToken.offset}] |$fullToken|'); |
| expect(incToken.type, fullToken.type); |
| expect(incToken.offset, fullToken.offset); |
| expect(incToken.length, fullToken.length); |
| expect(incToken.lexeme, fullToken.lexeme); |
| } |
| |
| static void _assertEqualTokens( |
| CompilationUnit incUnit, CompilationUnit fullUnit) { |
| Token incToken = incUnit.beginToken; |
| Token fullToken = fullUnit.beginToken; |
| while (incToken.type != TokenType.EOF && fullToken.type != TokenType.EOF) { |
| _assertEqualToken(incToken, fullToken); |
| // comments |
| { |
| Token incComment = incToken.precedingComments; |
| Token fullComment = fullToken.precedingComments; |
| while (true) { |
| if (fullComment == null) { |
| expect(incComment, isNull); |
| break; |
| } |
| expect(incComment, isNotNull); |
| _assertEqualToken(incComment, fullComment); |
| incComment = incComment.next; |
| fullComment = fullComment.next; |
| } |
| } |
| // next tokens |
| incToken = incToken.next; |
| fullToken = fullToken.next; |
| } |
| } |
| } |
| |
| @reflectiveTest |
| class ResolutionContextBuilderTest extends EngineTestCase { |
| void test_scopeFor_ClassDeclaration() { |
| Scope scope = _scopeFor(_createResolvedClassDeclaration()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is LibraryScope, LibraryScope, scope); |
| } |
| |
| void test_scopeFor_ClassTypeAlias() { |
| Scope scope = _scopeFor(_createResolvedClassTypeAlias()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is LibraryScope, LibraryScope, scope); |
| } |
| |
| void test_scopeFor_CompilationUnit() { |
| Scope scope = _scopeFor(_createResolvedCompilationUnit()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is LibraryScope, LibraryScope, scope); |
| } |
| |
| void test_scopeFor_ConstructorDeclaration() { |
| Scope scope = _scopeFor(_createResolvedConstructorDeclaration()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is ClassScope, ClassScope, scope); |
| } |
| |
| void test_scopeFor_ConstructorDeclaration_parameters() { |
| Scope scope = _scopeFor(_createResolvedConstructorDeclaration().parameters); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is FunctionScope, FunctionScope, scope); |
| } |
| |
| void test_scopeFor_FunctionDeclaration() { |
| Scope scope = _scopeFor(_createResolvedFunctionDeclaration()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is LibraryScope, LibraryScope, scope); |
| } |
| |
| void test_scopeFor_FunctionDeclaration_parameters() { |
| Scope scope = _scopeFor( |
| _createResolvedFunctionDeclaration().functionExpression.parameters); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is FunctionScope, FunctionScope, scope); |
| } |
| |
| void test_scopeFor_FunctionTypeAlias() { |
| Scope scope = _scopeFor(_createResolvedFunctionTypeAlias()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is LibraryScope, LibraryScope, scope); |
| } |
| |
| void test_scopeFor_FunctionTypeAlias_parameters() { |
| Scope scope = _scopeFor(_createResolvedFunctionTypeAlias().parameters); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is FunctionTypeScope, FunctionTypeScope, scope); |
| } |
| |
| void test_scopeFor_MethodDeclaration() { |
| Scope scope = _scopeFor(_createResolvedMethodDeclaration()); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is ClassScope, ClassScope, scope); |
| } |
| |
| void test_scopeFor_MethodDeclaration_body() { |
| Scope scope = _scopeFor(_createResolvedMethodDeclaration().body); |
| EngineTestCase.assertInstanceOf( |
| (obj) => obj is FunctionScope, FunctionScope, scope); |
| } |
| |
| void test_scopeFor_notInCompilationUnit() { |
| try { |
| _scopeFor(AstTestFactory.identifier3("x")); |
| fail("Expected AnalysisException"); |
| } on AnalysisException { |
| // Expected |
| } |
| } |
| |
| void test_scopeFor_null() { |
| try { |
| _scopeFor(null); |
| fail("Expected AnalysisException"); |
| } on AnalysisException { |
| // Expected |
| } |
| } |
| |
| void test_scopeFor_unresolved() { |
| try { |
| _scopeFor(AstTestFactory.compilationUnit()); |
| fail("Expected AnalysisException"); |
| } on AnalysisException { |
| // Expected |
| } |
| } |
| |
| ClassDeclaration _createResolvedClassDeclaration() { |
| CompilationUnit unit = _createResolvedCompilationUnit(); |
| String className = "C"; |
| ClassDeclaration classNode = AstTestFactory.classDeclaration( |
| null, className, AstTestFactory.typeParameterList(), null, null, null); |
| unit.declarations.add(classNode); |
| ClassElement classElement = ElementFactory.classElement2(className); |
| classNode.name.staticElement = classElement; |
| (unit.element as CompilationUnitElementImpl).types = <ClassElement>[ |
| classElement |
| ]; |
| return classNode; |
| } |
| |
| ClassTypeAlias _createResolvedClassTypeAlias() { |
| CompilationUnit unit = _createResolvedCompilationUnit(); |
| String className = "C"; |
| ClassTypeAlias classNode = AstTestFactory.classTypeAlias( |
| className, AstTestFactory.typeParameterList(), null, null, null, null); |
| unit.declarations.add(classNode); |
| ClassElement classElement = ElementFactory.classElement2(className); |
| classNode.name.staticElement = classElement; |
| (unit.element as CompilationUnitElementImpl).types = <ClassElement>[ |
| classElement |
| ]; |
| return classNode; |
| } |
| |
| CompilationUnit _createResolvedCompilationUnit() { |
| CompilationUnit unit = AstTestFactory.compilationUnit(); |
| LibraryElementImpl library = |
| ElementFactory.library(AnalysisContextFactory.contextWithCore(), "lib"); |
| unit.element = library.definingCompilationUnit; |
| return unit; |
| } |
| |
| ConstructorDeclaration _createResolvedConstructorDeclaration() { |
| ClassDeclaration classNode = _createResolvedClassDeclaration(); |
| String constructorName = "f"; |
| ConstructorDeclaration constructorNode = |
| AstTestFactory.constructorDeclaration( |
| AstTestFactory.identifier3(constructorName), |
| null, |
| AstTestFactory.formalParameterList(), |
| null); |
| classNode.members.add(constructorNode); |
| ConstructorElement constructorElement = |
| ElementFactory.constructorElement2(classNode.element, null); |
| constructorNode.element = constructorElement; |
| (classNode.element as ClassElementImpl).constructors = <ConstructorElement>[ |
| constructorElement |
| ]; |
| return constructorNode; |
| } |
| |
| FunctionDeclaration _createResolvedFunctionDeclaration() { |
| CompilationUnit unit = _createResolvedCompilationUnit(); |
| String functionName = "f"; |
| FunctionDeclaration functionNode = AstTestFactory.functionDeclaration( |
| null, null, functionName, AstTestFactory.functionExpression()); |
| unit.declarations.add(functionNode); |
| FunctionElement functionElement = |
| ElementFactory.functionElement(functionName); |
| functionNode.name.staticElement = functionElement; |
| (unit.element as CompilationUnitElementImpl).functions = <FunctionElement>[ |
| functionElement |
| ]; |
| return functionNode; |
| } |
| |
| FunctionTypeAlias _createResolvedFunctionTypeAlias() { |
| CompilationUnit unit = _createResolvedCompilationUnit(); |
| FunctionTypeAlias aliasNode = AstTestFactory.typeAlias( |
| AstTestFactory.typeName4("A"), |
| "F", |
| AstTestFactory.typeParameterList(), |
| AstTestFactory.formalParameterList()); |
| unit.declarations.add(aliasNode); |
| SimpleIdentifier aliasName = aliasNode.name; |
| FunctionTypeAliasElement aliasElement = |
| new FunctionTypeAliasElementImpl.forNode(aliasName); |
| aliasName.staticElement = aliasElement; |
| (unit.element as CompilationUnitElementImpl).typeAliases = |
| <FunctionTypeAliasElement>[aliasElement]; |
| return aliasNode; |
| } |
| |
| MethodDeclaration _createResolvedMethodDeclaration() { |
| ClassDeclaration classNode = _createResolvedClassDeclaration(); |
| String methodName = "f"; |
| MethodDeclaration methodNode = AstTestFactory.methodDeclaration( |
| null, |
| null, |
| null, |
| null, |
| AstTestFactory.identifier3(methodName), |
| AstTestFactory.formalParameterList()); |
| classNode.members.add(methodNode); |
| MethodElement methodElement = |
| ElementFactory.methodElement(methodName, null); |
| methodNode.name.staticElement = methodElement; |
| (classNode.element as ClassElementImpl).methods = <MethodElement>[ |
| methodElement |
| ]; |
| return methodNode; |
| } |
| |
| Scope _scopeFor(AstNode node) { |
| return ResolutionContextBuilder.contextFor(node).scope; |
| } |
| } |
| |
| class _Edit { |
| final int offset; |
| final int length; |
| final String replacement; |
| _Edit(this.offset, this.length, this.replacement); |
| } |
| |
| class _TestLogger implements logging.Logger { |
| Object lastException; |
| Object lastStackTrace; |
| |
| @override |
| void enter(String name) {} |
| |
| @override |
| void exit() {} |
| |
| void expectNoErrors() { |
| if (lastException != null) { |
| fail("logged an exception:\n$lastException\n$lastStackTrace\n"); |
| } |
| } |
| |
| @override |
| void log(Object obj) {} |
| |
| @override |
| void logException(Object exception, [Object stackTrace]) { |
| lastException = exception; |
| lastStackTrace = stackTrace; |
| } |
| |
| @override |
| logging.LoggingTimer startTimer() { |
| return new logging.LoggingTimer(this); |
| } |
| } |