Implement more fixes.

R=paulberry@google.com
BUG=

Review URL: https://codereview.chromium.org//418203002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@38571 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
scheglov@google.com
2014-07-25 03:21:09 +00:00
parent 9c2b650c15
commit 33e44b90b0
9 changed files with 1452 additions and 525 deletions
@@ -95,7 +95,7 @@ class EditDomainHandler implements RequestHandler {
engine.AnalysisErrorInfo errorInfo = server.getErrors(file);
if (errorInfo != null) {
for (engine.AnalysisError error in errorInfo.errors) {
List<Fix> fixes = computeFixes(searchEngine, file, unit, error);
List<Fix> fixes = computeFixes(searchEngine, unit, error);
if (fixes.isNotEmpty) {
AnalysisError serverError =
new AnalysisError.fromEngine(errorInfo.lineInfo, error);
+1 -1
View File
@@ -117,7 +117,7 @@ main() {
engine.AnalysisErrorInfo errors = context.getErrors(testSource);
engine.AnalysisError engineError = errors.errors[0];
List<services.Fix> servicesFixes =
services.computeFixes(searchEngine, testFile, testUnit, engineError);
services.computeFixes(searchEngine, testUnit, engineError);
AnalysisError error =
new AnalysisError.fromEngine(errors.lineInfo, engineError);
ErrorFixes fixes = new ErrorFixes(error);
@@ -37,6 +37,11 @@ class Change implements HasToJson {
final List<LinkedPositionGroup> linkedPositionGroups = <LinkedPositionGroup>[
];
/**
* An optional position to move selection to after applying this change.
*/
Position endPosition;
Change(this.message);
/**
@@ -9,6 +9,7 @@ import 'package:analysis_services/search/search_engine.dart';
import 'package:analysis_services/src/correction/fix.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/source.dart';
/**
@@ -16,9 +17,11 @@ import 'package:analyzer/src/generated/error.dart';
*
* Returns the computed [Fix]s, not `null`.
*/
List<Fix> computeFixes(SearchEngine searchEngine, String file,
List<Fix> computeFixes(SearchEngine searchEngine,
CompilationUnit unit, AnalysisError error) {
var processor = new FixProcessor(searchEngine, file, unit, error);
Source source = unit.element.source;
String file = source.fullName;
var processor = new FixProcessor(searchEngine, source, file, unit, error);
return processor.compute();
}
File diff suppressed because it is too large Load Diff
@@ -26,6 +26,10 @@ class SourceBuilder {
SourceBuilder(this.file, this.offset);
SourceBuilder.buffer() : file = null, offset = 0;
int get length => _buffer.length;
void addProposal(String proposal) {
// TODO(scheglov) implement
// _currentPositionGroup.addProposal();
@@ -58,6 +58,19 @@ String removeStart(String str, String remove) {
return str;
}
int compareStrings(String a, String b) {
if (a == b) {
return 0;
}
if (a == null) {
return 1;
}
if (b == null) {
return -1;
}
return a.compareTo(b);
}
String repeat(String s, int n) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < n; i++) {
@@ -11,6 +11,7 @@ import 'package:analysis_services/src/correction/source_range.dart';
import 'package:analysis_services/src/correction/strings.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
@@ -36,6 +37,26 @@ String getDefaultValueCode(DartType type) {
}
/**
* @return the [ExecutableElement] of the enclosing executable [AstNode].
*/
ExecutableElement getEnclosingExecutableElement(AstNode node) {
while (node != null) {
if (node is FunctionDeclaration) {
return node.element;
}
if (node is ConstructorDeclaration) {
return node.element;
}
if (node is MethodDeclaration) {
return node.element;
}
node = node.parent;
}
return null;
}
/**
* Returns [getExpressionPrecedence] for the parent of [node],
* or `0` if the parent node is [ParenthesizedExpression].
@@ -71,6 +92,34 @@ Map<String, Element> getImportNamespace(ImportElement imp) {
return namespace.definedNames;
}
/**
* If given [AstNode] is name of qualified property extraction, returns target from which
* this property is extracted. Otherwise `null`.
*/
Expression getQualifiedPropertyTarget(AstNode node) {
AstNode parent = node.parent;
if (parent is PrefixedIdentifier) {
PrefixedIdentifier prefixed = parent;
if (identical(prefixed.identifier, node)) {
return parent.prefix;
}
}
if (parent is PropertyAccess) {
PropertyAccess access = parent;
if (identical(access.propertyName, node)) {
return access.realTarget;
}
}
return null;
}
/**
* Returns the [String] content of the given [Source].
*/
String getSourceContent(AnalysisContext context, Source source) {
return context.getContents(source).data;
}
class CorrectionUtils {
final CompilationUnit unit;
@@ -99,6 +148,26 @@ class CorrectionUtils {
return _endOfLine;
}
/**
* Returns the actual type source of the given [Expression], may be `null`
* if can not be resolved, should be treated as the `dynamic` type.
*/
String getExpressionTypeSource(Expression expression) {
if (expression == null) {
return null;
}
DartType type = expression.bestType;
if (type.isDynamic) {
return null;
}
return getTypeSource(type);
}
/**
* Returns the indentation with the given level.
*/
String getIndent(int level) => repeat(' ', level);
/**
* Skips whitespace characters and single EOL on the right from [index].
*
@@ -144,6 +213,40 @@ class CorrectionUtils {
return index;
}
/**
* Returns the whitespace prefix of the line which contains given offset.
*/
String getLinePrefix(int index) {
int lineStart = getLineThis(index);
int length = _buffer.length;
int lineNonWhitespace = lineStart;
while (lineNonWhitespace < length) {
int c = _buffer.codeUnitAt(lineNonWhitespace);
if (c == 0xD || c == 0xA) {
break;
}
if (!isWhitespace(c)) {
break;
}
lineNonWhitespace++;
}
return getText2(lineStart, lineNonWhitespace - lineStart);
}
/**
* Returns the start index of the line which contains given index.
*/
int getLineThis(int index) {
while (index > 0) {
int c = _buffer.codeUnitAt(index - 1);
if (c == 0xD || c == 0xA) {
break;
}
index--;
}
return index;
}
/**
* Returns a [SourceRange] that covers [range] and extends (if possible) to
* cover whole lines.
@@ -159,6 +262,20 @@ class CorrectionUtils {
return rangeStartEnd(startLineOffset, afterEndLineOffset);
}
/**
* Returns the line prefix consisting of spaces and tabs on the left from the given
* [AstNode].
*/
String getNodePrefix(AstNode node) {
int offset = node.offset;
// function literal is special, it uses offset of enclosing line
if (node is FunctionExpression) {
return getLinePrefix(offset);
}
// use just prefix directly before node
return getPrefix(offset);
}
/**
* @return the source for the parameter with the given type and name.
*/
@@ -198,21 +315,25 @@ class CorrectionUtils {
}
/**
* Returns the actual type source of the given [Expression], may be `null`
* if can not be resolved, should be treated as the `dynamic` type.
* Returns the line prefix consisting of spaces and tabs on the left from the
* given offset.
*/
String getExpressionTypeSource(Expression expression) {
if (expression == null) {
return null;
}
DartType type = expression.bestType;
String typeSource = getTypeSource(type);
if ("dynamic" == typeSource) {
return null;
}
return typeSource;
String getPrefix(int endIndex) {
int startIndex = getLineContentStart(endIndex);
return _buffer.substring(startIndex, endIndex);
}
/**
* Returns the text of the given [AstNode] in the unit.
*/
String getText(AstNode node) => getText2(node.offset, node.length);
/**
* Returns the text of the given range in the unit.
*/
String getText2(int offset, int length) =>
_buffer.substring(offset, offset + length);
/**
* Returns the source to reference [type] in this [CompilationUnit].
*/
@@ -59,7 +59,7 @@ class FixProcessorTest extends AbstractSingleUnitTest {
void assertNoFix(FixKind kind) {
AnalysisError error = _findErrorToFix();
List<Fix> fixes = computeFixes(searchEngine, testFile, testUnit, error);
List<Fix> fixes = computeFixes(searchEngine, testUnit, error);
for (Fix fix in fixes) {
if (fix.kind == kind) {
throw fail('Unexpected fix $kind in\n${fixes.join('\n')}');
@@ -242,6 +242,94 @@ class B extends A {
assertNoFix(FixKind.ADD_SUPER_CONSTRUCTOR_INVOCATION);
}
void test_createConstructorSuperImplicit() {
_indexTestUnit('''
class A {
A(p1, int p2, List<String> p3, [int p4]);
}
class B extends A {
int existingField;
void existingMethod() {}
}
''');
assertHasFix(FixKind.CREATE_CONSTRUCTOR_SUPER, '''
class A {
A(p1, int p2, List<String> p3, [int p4]);
}
class B extends A {
int existingField;
B(p1, int p2, List<String> p3) : super(p1, p2, p3);
void existingMethod() {}
}
''');
}
void test_createConstructorSuperImplicit_fieldInitializer() {
_indexTestUnit('''
class A {
int _field;
A(this._field);
}
class B extends A {
int existingField;
void existingMethod() {}
}
''');
assertHasFix(FixKind.CREATE_CONSTRUCTOR_SUPER, '''
class A {
int _field;
A(this._field);
}
class B extends A {
int existingField;
B(int field) : super(field);
void existingMethod() {}
}
''');
}
void test_createConstructorSuperImplicit_named() {
_indexTestUnit('''
class A {
A.named(p1, int p2);
}
class B extends A {
int existingField;
void existingMethod() {}
}
''');
assertHasFix(FixKind.CREATE_CONSTRUCTOR_SUPER, '''
class A {
A.named(p1, int p2);
}
class B extends A {
int existingField;
B.named(p1, int p2) : super.named(p1, p2);
void existingMethod() {}
}
''');
}
void test_createConstructorSuperImplicit_private() {
_indexTestUnit('''
class A {
A._named(p);
}
class B extends A {
}
''');
assertNoFix(FixKind.CREATE_CONSTRUCTOR_SUPER);
}
void test_createConstructor_insteadOfSyntheticDefault() {
_indexTestUnit('''
class A {
@@ -290,6 +378,455 @@ main() {
''');
}
void test_createMissingOverrides_functionType() {
_indexTestUnit('''
abstract class A {
forEach(int f(double p1, String p2));
}
class B extends A {
}
''');
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, '''
abstract class A {
forEach(int f(double p1, String p2));
}
class B extends A {
@override
forEach(int f(double p1, String p2)) {
// TODO: implement forEach
}
}
''');
}
void test_createMissingOverrides_generics() {
_indexTestUnit('''
class Iterator<T> {
}
abstract class IterableMixin<T> {
Iterator<T> get iterator;
}
class Test extends IterableMixin<int> {
}
''');
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, '''
class Iterator<T> {
}
abstract class IterableMixin<T> {
Iterator<T> get iterator;
}
class Test extends IterableMixin<int> {
// TODO: implement iterator
@override
Iterator<int> get iterator => null;
}
''');
}
void test_createMissingOverrides_getter() {
_indexTestUnit('''
abstract class A {
get g1;
int get g2;
}
class B extends A {
}
''');
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, '''
abstract class A {
get g1;
int get g2;
}
class B extends A {
// TODO: implement g1
@override
get g1 => null;
// TODO: implement g2
@override
int get g2 => null;
}
''');
}
void test_createMissingOverrides_importPrefix() {
_indexTestUnit('''
import 'dart:async' as aaa;
abstract class A {
Map<aaa.Future, List<aaa.Future>> g(aaa.Future p);
}
class B extends A {
}
''');
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, '''
import 'dart:async' as aaa;
abstract class A {
Map<aaa.Future, List<aaa.Future>> g(aaa.Future p);
}
class B extends A {
@override
Map<aaa.Future, List<aaa.Future>> g(aaa.Future p) {
// TODO: implement g
}
}
''');
}
void test_createMissingOverrides_method() {
_indexTestUnit('''
abstract class A {
m1();
int m2();
String m3(int p1, double p2, Map<int, List<String>> p3);
String m4(p1, p2);
String m5(p1, [int p2 = 2, int p3, p4 = 4]);
String m6(p1, {int p2: 2, int p3, p4: 4});
}
class B extends A {
}
''');
String expectedCode = '''
abstract class A {
m1();
int m2();
String m3(int p1, double p2, Map<int, List<String>> p3);
String m4(p1, p2);
String m5(p1, [int p2 = 2, int p3, p4 = 4]);
String m6(p1, {int p2: 2, int p3, p4: 4});
}
class B extends A {
@override
m1() {
// TODO: implement m1
}
@override
int m2() {
// TODO: implement m2
}
@override
String m3(int p1, double p2, Map<int, List<String>> p3) {
// TODO: implement m3
}
@override
String m4(p1, p2) {
// TODO: implement m4
}
@override
String m5(p1, [int p2 = 2, int p3, p4 = 4]) {
// TODO: implement m5
}
@override
String m6(p1, {int p2: 2, int p3, p4: 4}) {
// TODO: implement m6
}
}
''';
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, expectedCode);
// end position should be on "m1", not on "m2", "m3", etc
{
Position endPosition = change.endPosition;
expect(endPosition, isNotNull);
expect(endPosition.file, testFile);
int endOffset = endPosition.offset;
String endString = expectedCode.substring(endOffset, endOffset + 25);
expect(endString, contains('m1'));
expect(endString, isNot(contains('m2')));
expect(endString, isNot(contains('m3')));
expect(endString, isNot(contains('m4')));
expect(endString, isNot(contains('m5')));
expect(endString, isNot(contains('m6')));
}
}
void test_createMissingOverrides_operator() {
_indexTestUnit('''
abstract class A {
int operator [](int index);
void operator []=(int index, String value);
}
class B extends A {
}
''');
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, '''
abstract class A {
int operator [](int index);
void operator []=(int index, String value);
}
class B extends A {
@override
int operator [](int index) {
// TODO: implement []
}
@override
void operator []=(int index, String value) {
// TODO: implement []=
}
}
''');
}
void test_createMissingOverrides_setter() {
_indexTestUnit('''
abstract class A {
set s1(x);
set s2(int x);
void set s3(String x);
}
class B extends A {
}
''');
assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, '''
abstract class A {
set s1(x);
set s2(int x);
void set s3(String x);
}
class B extends A {
@override
set s1(x) {
// TODO: implement s1
}
@override
set s2(int x) {
// TODO: implement s2
}
@override
void set s3(String x) {
// TODO: implement s3
}
}
''');
}
void test_createNoSuchMethod() {
_indexTestUnit('''
abstract class A {
m1();
int m2();
}
class B extends A {
existing() {}
}
''');
assertHasFix(FixKind.CREATE_NO_SUCH_METHOD, '''
abstract class A {
m1();
int m2();
}
class B extends A {
existing() {}
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
''');
}
void test_creationFunction_forFunctionType_cascadeSecond() {
_indexTestUnit('''
class A {
B ma() => null;
}
class B {
useFunction(int g(double a, String b)) {}
}
main() {
A a = new A();
a..ma().useFunction(test);
}
''');
assertHasFix(FixKind.CREATE_FUNCTION, '''
class A {
B ma() => null;
}
class B {
useFunction(int g(double a, String b)) {}
}
main() {
A a = new A();
a..ma().useFunction(test);
}
int test(double a, String b) {
}
''');
}
void test_creationFunction_forFunctionType_dynamicArgument() {
_indexTestUnit('''
main() {
useFunction(test);
}
useFunction(int g(a, b)) {}
''');
assertHasFix(FixKind.CREATE_FUNCTION, '''
main() {
useFunction(test);
}
useFunction(int g(a, b)) {}
int test(a, b) {
}
''');
}
void test_creationFunction_forFunctionType_function() {
_indexTestUnit('''
main() {
useFunction(test);
}
useFunction(int g(double a, String b)) {}
''');
assertHasFix(FixKind.CREATE_FUNCTION, '''
main() {
useFunction(test);
}
useFunction(int g(double a, String b)) {}
int test(double a, String b) {
}
''');
}
void test_creationFunction_forFunctionType_method_enclosingClass_static() {
_indexTestUnit('''
class A {
static foo() {
useFunction(test);
}
}
useFunction(int g(double a, String b)) {}
''');
assertHasFix(FixKind.CREATE_METHOD, '''
class A {
static foo() {
useFunction(test);
}
static int test(double a, String b) {
}
}
useFunction(int g(double a, String b)) {}
''');
}
void test_creationFunction_forFunctionType_method_enclosingClass_static2() {
_indexTestUnit('''
class A {
var f;
A() : f = useFunction(test);
}
useFunction(int g(double a, String b)) {}
''');
assertHasFix(FixKind.CREATE_METHOD, '''
class A {
var f;
A() : f = useFunction(test);
static int test(double a, String b) {
}
}
useFunction(int g(double a, String b)) {}
''');
}
void test_creationFunction_forFunctionType_method_targetClass() {
_indexTestUnit('''
main(A a) {
useFunction(a.test);
}
class A {
}
useFunction(int g(double a, String b)) {}
''');
assertHasFix(FixKind.CREATE_METHOD, '''
main(A a) {
useFunction(a.test);
}
class A {
int test(double a, String b) {
}
}
useFunction(int g(double a, String b)) {}
''');
}
void
test_creationFunction_forFunctionType_method_targetClass_hasOtherMember() {
_indexTestUnit('''
main(A a) {
useFunction(a.test);
}
class A {
m() {}
}
useFunction(int g(double a, String b)) {}
''');
assertHasFix(FixKind.CREATE_METHOD, '''
main(A a) {
useFunction(a.test);
}
class A {
m() {}
int test(double a, String b) {
}
}
useFunction(int g(double a, String b)) {}
''');
}
void test_creationFunction_forFunctionType_notFunctionType() {
_indexTestUnit('''
main(A a) {
useFunction(a.test);
}
typedef A();
useFunction(g) {}
''');
assertNoFix(FixKind.CREATE_METHOD);
assertNoFix(FixKind.CREATE_FUNCTION);
}
void test_creationFunction_forFunctionType_unknownTarget() {
_indexTestUnit('''
main(A a) {
useFunction(a.test);
}
class A {
}
useFunction(g) {}
''');
assertNoFix(FixKind.CREATE_METHOD);
}
void test_expectedToken_semicolon() {
_indexTestUnit('''
main() {
@@ -465,6 +1002,56 @@ const a = const A();
''');
}
void test_undefinedMethod_createQualified_fromClass() {
_indexTestUnit('''
class A {
}
main() {
A.myUndefinedMethod();
}
''');
assertHasFix(FixKind.CREATE_METHOD, '''
class A {
static void myUndefinedMethod() {
}
}
main() {
A.myUndefinedMethod();
}
''');
}
void test_undefinedMethod_createQualified_fromClass_hasOtherMember() {
_indexTestUnit('''
class A {
foo() {}
}
main() {
A.myUndefinedMethod();
}
''');
assertHasFix(FixKind.CREATE_METHOD, '''
class A {
foo() {}
static void myUndefinedMethod() {
}
}
main() {
A.myUndefinedMethod();
}
''');
}
void test_undefinedMethod_createQualified_fromClass_unresolved() {
_indexTestUnit('''
main() {
NoSuchClass.myUndefinedMethod();
}
''');
assertNoFix(FixKind.CREATE_METHOD);
}
void test_useEffectiveIntegerDivision() {
_indexTestUnit('''
main() {
@@ -496,7 +1083,7 @@ main() {
* Computes fixes and verifies that there is a fix of the given kind.
*/
Fix _assertHasFix(FixKind kind, AnalysisError error) {
List<Fix> fixes = computeFixes(searchEngine, testFile, testUnit, error);
List<Fix> fixes = computeFixes(searchEngine, testUnit, error);
for (Fix fix in fixes) {
if (fix.kind == kind) {
return fix;