From 33e44b90b0b426b134e16045cee4c1935ffe347b Mon Sep 17 00:00:00 2001 From: "scheglov@google.com" Date: Fri, 25 Jul 2014 03:21:09 +0000 Subject: [PATCH] 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 --- .../lib/src/edit/edit_domain.dart | 2 +- pkg/analysis_server/test/edit/fix_test.dart | 2 +- .../lib/correction/change.dart | 5 + pkg/analysis_services/lib/correction/fix.dart | 7 +- .../lib/src/correction/fix.dart | 1208 ++++++++++------- .../lib/src/correction/source_buffer.dart | 4 + .../lib/src/correction/strings.dart | 13 + .../lib/src/correction/util.dart | 145 +- .../test/correction/fix_test.dart | 591 +++++++- 9 files changed, 1452 insertions(+), 525 deletions(-) diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart index 68a3dbc6f91..8bcc01c7642 100644 --- a/pkg/analysis_server/lib/src/edit/edit_domain.dart +++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart @@ -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 fixes = computeFixes(searchEngine, file, unit, error); + List fixes = computeFixes(searchEngine, unit, error); if (fixes.isNotEmpty) { AnalysisError serverError = new AnalysisError.fromEngine(errorInfo.lineInfo, error); diff --git a/pkg/analysis_server/test/edit/fix_test.dart b/pkg/analysis_server/test/edit/fix_test.dart index 60ec94de188..20ce5264b4d 100644 --- a/pkg/analysis_server/test/edit/fix_test.dart +++ b/pkg/analysis_server/test/edit/fix_test.dart @@ -117,7 +117,7 @@ main() { engine.AnalysisErrorInfo errors = context.getErrors(testSource); engine.AnalysisError engineError = errors.errors[0]; List 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); diff --git a/pkg/analysis_services/lib/correction/change.dart b/pkg/analysis_services/lib/correction/change.dart index a352855dd14..2135d133a0d 100644 --- a/pkg/analysis_services/lib/correction/change.dart +++ b/pkg/analysis_services/lib/correction/change.dart @@ -37,6 +37,11 @@ class Change implements HasToJson { final List linkedPositionGroups = [ ]; + /** + * An optional position to move selection to after applying this change. + */ + Position endPosition; + Change(this.message); /** diff --git a/pkg/analysis_services/lib/correction/fix.dart b/pkg/analysis_services/lib/correction/fix.dart index f1652135254..eaafa6db1d6 100644 --- a/pkg/analysis_services/lib/correction/fix.dart +++ b/pkg/analysis_services/lib/correction/fix.dart @@ -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 computeFixes(SearchEngine searchEngine, String file, +List 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(); } diff --git a/pkg/analysis_services/lib/src/correction/fix.dart b/pkg/analysis_services/lib/src/correction/fix.dart index fe24a149c11..0f9eb9495fc 100644 --- a/pkg/analysis_services/lib/src/correction/fix.dart +++ b/pkg/analysis_services/lib/src/correction/fix.dart @@ -13,12 +13,14 @@ import 'package:analysis_services/search/search_engine.dart'; import 'package:analysis_services/src/correction/name_suggestion.dart'; import 'package:analysis_services/src/correction/source_buffer.dart'; import 'package:analysis_services/src/correction/source_range.dart' as rf; +import 'package:analysis_services/src/correction/strings.dart'; import 'package:analysis_services/src/correction/util.dart'; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/element.dart'; import 'package:analyzer/src/generated/error.dart'; import 'package:analyzer/src/generated/java_core.dart'; import 'package:analyzer/src/generated/parser.dart'; +import 'package:analyzer/src/generated/scanner.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/utilities_dart.dart'; @@ -28,13 +30,17 @@ import 'package:analyzer/src/generated/utilities_dart.dart'; */ class FixProcessor { final SearchEngine searchEngine; + final Source source; final String file; final CompilationUnit unit; final AnalysisError error; + CompilationUnitElement unitElement; + LibraryElement unitLibraryElement; final List edits = []; final Map linkedPositionGroups = {}; + Position endPosition = null; final List fixes = []; CorrectionUtils utils; @@ -45,7 +51,18 @@ class FixProcessor { AstNode coveredNode; - FixProcessor(this.searchEngine, this.file, this.unit, this.error); + FixProcessor(this.searchEngine, this.source, this.file, this.unit, this.error) + { + unitElement = unit.element; + unitLibraryElement = unitElement.library; + } + + DartType get coreTypeBool => _getCoreType("bool"); + + /** + * Returns the EOL to use for this [CompilationUnit]. + */ + String get eol => utils.endOfLine; List compute() { utils = new CorrectionUtils(unit); @@ -69,11 +86,10 @@ class FixProcessor { CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT) { _addFix_createConstructorSuperExplicit(); } -// if (identical( -// errorCode, -// CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT)) { -// _addFix_createConstructorSuperImplicit(); -// } + if (errorCode == + CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT) { + _addFix_createConstructorSuperImplicit(); + } if (errorCode == CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT) { _addFix_createConstructorSuperExplicit(); @@ -139,11 +155,11 @@ class FixProcessor { _addFix_createClass(); _addFix_undefinedClass_useSimilar(); } -// if (identical(errorCode, StaticWarningCode.UNDEFINED_IDENTIFIER)) { -// _addFix_createFunction_forFunctionType(); -// _addFix_importLibrary_withType(); -// _addFix_importLibrary_withTopLevelVariable(); -// } + if (errorCode == StaticWarningCode.UNDEFINED_IDENTIFIER) { + _addFix_createFunction_forFunctionType(); + _addFix_importLibrary_withType(); + _addFix_importLibrary_withTopLevelVariable(); + } if (errorCode == StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER) { _addFix_useStaticAccess_method(); _addFix_useStaticAccess_property(); @@ -156,15 +172,15 @@ class FixProcessor { // _addFix_undefinedFunction_useSimilar(); // _addFix_undefinedFunction_create(); // } -// if (identical(errorCode, StaticTypeWarningCode.UNDEFINED_GETTER)) { -// _addFix_createFunction_forFunctionType(); -// } -// if (identical(errorCode, HintCode.UNDEFINED_METHOD) || -// identical(errorCode, StaticTypeWarningCode.UNDEFINED_METHOD)) { -// _addFix_undefinedMethod_useSimilar(); -// _addFix_undefinedMethod_create(); -// _addFix_undefinedFunction_create(); -// } + if (errorCode == StaticTypeWarningCode.UNDEFINED_GETTER) { + _addFix_createFunction_forFunctionType(); + } + if (errorCode == HintCode.UNDEFINED_METHOD || + errorCode == StaticTypeWarningCode.UNDEFINED_METHOD) { + _addFix_undefinedMethod_useSimilar(); + _addFix_undefinedMethod_create(); + _addFix_undefinedFunction_create(); + } // done return fixes; } @@ -181,11 +197,17 @@ class FixProcessor { change.add(fileEdit); linkedPositionGroups.values.forEach( (group) => change.addLinkedPositionGroup(group)); + change.endPosition = endPosition; // add Fix Fix fix = new Fix(kind, change); fixes.add(fix); + // clear + edits.clear(); + linkedPositionGroups.clear(); + endPosition = null; } + void _addFix_addPackageDependency() { // TODO(scheglov) implement // if (node is SimpleStringLiteral && node.parent is NamespaceDirective) { @@ -208,18 +230,17 @@ class FixProcessor { // } } + void _addFix_boolInsteadOfBoolean() { SourceRange range = rf.rangeError(error); _addReplaceEdit(range, "bool"); _addFix(FixKind.REPLACE_BOOLEAN_WITH_BOOL, []); } - void _addFix_createClass() { if (_mayBeTypeIdentifier(node)) { String name = (node as SimpleIdentifier).name; // prepare environment - String eol = utils.endOfLine; CompilationUnitMember enclosingMember = node.getAncestor((node) => node is CompilationUnitMember); int offset = enclosingMember.end; @@ -250,7 +271,6 @@ class FixProcessor { } } - void _addFix_createConstructorSuperExplicit() { ConstructorDeclaration targetConstructor = node.parent as ConstructorDeclaration; @@ -319,79 +339,75 @@ class FixProcessor { } void _addFix_createConstructorSuperImplicit() { - // TODO(scheglov) implement -// ClassDeclaration targetClassNode = node.parent as ClassDeclaration; -// ClassElement targetClassElement = targetClassNode.element; -// ClassElement superClassElement = targetClassElement.supertype.element; -// String targetClassName = targetClassElement.name; -// // add proposals for all super constructors -// List superConstructors = superClassElement.constructors; -// for (ConstructorElement superConstructor in superConstructors) { -// String constructorName = superConstructor.name; -// // skip private -// if (Identifier.isPrivateName(constructorName)) { -// continue; -// } -// // prepare parameters and arguments -// JavaStringBuilder parametersBuffer = new JavaStringBuilder(); -// JavaStringBuilder argumentsBuffer = new JavaStringBuilder(); -// bool firstParameter = true; -// for (ParameterElement parameter in superConstructor.parameters) { -// // skip non-required parameters -// if (parameter.parameterKind != ParameterKind.REQUIRED) { -// break; -// } -// // comma -// if (firstParameter) { -// firstParameter = false; -// } else { -// parametersBuffer.append(", "); -// argumentsBuffer.append(", "); -// } -// // name -// String parameterName = parameter.displayName; -// if (parameterName.length > 1 && parameterName.startsWith("_")) { -// parameterName = parameterName.substring(1); -// } -// // parameter & argument -// _appendParameterSource(parametersBuffer, parameter.type, parameterName); -// argumentsBuffer.append(parameterName); -// } -// // add proposal -// String eol = utils.endOfLine; -// QuickFixProcessorImpl_NewConstructorLocation targetLocation = -// _prepareNewConstructorLocation(targetClassNode, eol); -// SourceBuilder sb = new SourceBuilder.con1(targetLocation._offset); -// { -// String indent = utils.getIndent(1); -// sb.append(targetLocation._prefix); -// sb.append(indent); -// sb.append(targetClassName); -// if (!constructorName.isEmpty) { -// sb.startPosition("NAME"); -// sb.append("."); -// sb.append(constructorName); -// sb.endPosition(); -// } -// sb.append("("); -// sb.append(parametersBuffer.toString()); -// sb.append(") : super"); -// if (!constructorName.isEmpty) { -// sb.append("."); -// sb.append(constructorName); -// } -// sb.append("("); -// sb.append(argumentsBuffer.toString()); -// sb.append(");"); -// sb.append(targetLocation._suffix); -// } -// _addInsertEdit3(sb); -// // add proposal -// String proposalName = _getConstructorProposalName(superConstructor); -// _addFix( -// FixKind.CREATE_CONSTRUCTOR_SUPER, -// [proposalName]); -// } + ClassDeclaration targetClassNode = node.parent as ClassDeclaration; + ClassElement targetClassElement = targetClassNode.element; + ClassElement superClassElement = targetClassElement.supertype.element; + String targetClassName = targetClassElement.name; + // add proposals for all super constructors + List superConstructors = superClassElement.constructors; + for (ConstructorElement superConstructor in superConstructors) { + String constructorName = superConstructor.name; + // skip private + if (Identifier.isPrivateName(constructorName)) { + continue; + } + // prepare parameters and arguments + SourceBuilder parametersBuffer = new SourceBuilder.buffer(); + SourceBuilder argumentsBuffer = new SourceBuilder.buffer(); + bool firstParameter = true; + for (ParameterElement parameter in superConstructor.parameters) { + // skip non-required parameters + if (parameter.parameterKind != ParameterKind.REQUIRED) { + break; + } + // comma + if (firstParameter) { + firstParameter = false; + } else { + parametersBuffer.append(', '); + argumentsBuffer.append(', '); + } + // name + String parameterName = parameter.displayName; + if (parameterName.length > 1 && parameterName.startsWith('_')) { + parameterName = parameterName.substring(1); + } + // parameter & argument + _appendParameterSource(parametersBuffer, parameter.type, parameterName); + argumentsBuffer.append(parameterName); + } + // add proposal + _ConstructorLocation targetLocation = + _prepareNewConstructorLocation(targetClassNode); + SourceBuilder sb = new SourceBuilder(file, targetLocation._offset); + { + String indent = utils.getIndent(1); + sb.append(targetLocation._prefix); + sb.append(indent); + sb.append(targetClassName); + if (!constructorName.isEmpty) { + sb.startPosition('NAME'); + sb.append('.'); + sb.append(constructorName); + sb.endPosition(); + } + sb.append("("); + sb.append(parametersBuffer.toString()); + sb.append(') : super'); + if (!constructorName.isEmpty) { + sb.append('.'); + sb.append(constructorName); + } + sb.append('('); + sb.append(argumentsBuffer.toString()); + sb.append(');'); + sb.append(targetLocation._suffix); + } + _insertBuilder(sb); + // add proposal + String proposalName = _getConstructorProposalName(superConstructor); + _addFix(FixKind.CREATE_CONSTRUCTOR_SUPER, [proposalName]); + } } void _addFix_createConstructor_insteadOfSyntheticDefault() { @@ -428,8 +444,6 @@ class FixProcessor { if (instanceCreation == null) { return; } - // prepare environment - String eol = utils.endOfLine; // prepare target DartType targetType = typeName.type; if (targetType is! InterfaceType) { @@ -438,8 +452,8 @@ class FixProcessor { ClassElement targetElement = targetType.element as ClassElement; String targetFile = targetElement.source.fullName; ClassDeclaration targetClass = targetElement.node; - QuickFixProcessorImpl_NewConstructorLocation targetLocation = - _prepareNewConstructorLocation(targetClass, eol); + _ConstructorLocation targetLocation = + _prepareNewConstructorLocation(targetClass); // build method source SourceBuilder sb = new SourceBuilder(targetFile, targetLocation._offset); { @@ -485,8 +499,6 @@ class FixProcessor { if (instanceCreation == null) { return; } - // prepare environment - String eol = utils.endOfLine; // prepare target interface type DartType targetType = constructorName.type.type; if (targetType is! InterfaceType) { @@ -495,8 +507,8 @@ class FixProcessor { ClassElement targetElement = targetType.element as ClassElement; String targetFile = targetElement.source.fullName; ClassDeclaration targetClass = targetElement.node; - QuickFixProcessorImpl_NewConstructorLocation targetLocation = - _prepareNewConstructorLocation(targetClass, eol); + _ConstructorLocation targetLocation = + _prepareNewConstructorLocation(targetClass); // build method source SourceBuilder sb = new SourceBuilder(targetFile, targetLocation._offset); { @@ -527,173 +539,156 @@ class FixProcessor { } void _addFix_createFunction_forFunctionType() { - // TODO(scheglov) implement -// if (node is SimpleIdentifier) { -// SimpleIdentifier nameNode = node as SimpleIdentifier; -// // prepare argument expression (to get parameter) -// ClassElement targetElement; -// Expression argument; -// { -// Expression target = CorrectionUtils.getQualifiedPropertyTarget(node); -// if (target != null) { -// DartType targetType = target.bestType; -// if (targetType != null && targetType.element is ClassElement) { -// targetElement = targetType.element as ClassElement; -// argument = target.parent as Expression; -// } else { -// return; -// } -// } else { -// ClassDeclaration enclosingClass = -// node.getAncestor((node) => node is ClassDeclaration); -// targetElement = enclosingClass != null ? -// enclosingClass.element : -// null; -// argument = nameNode; -// } -// } -// // should be argument of some invocation -// ParameterElement parameterElement = argument.bestParameterElement; -// if (parameterElement == null) { -// return; -// } -// // should be parameter of function type -// DartType parameterType = parameterElement.type; -// if (parameterType is! FunctionType) { -// return; -// } -// FunctionType functionType = parameterType as FunctionType; -// // add proposal -// if (targetElement != null) { -// _addProposal_createFunction_method(targetElement, functionType); -// } else { -// _addProposal_createFunction_function(functionType); -// } -// } + if (node is SimpleIdentifier) { + SimpleIdentifier nameNode = node as SimpleIdentifier; + // prepare argument expression (to get parameter) + ClassElement targetElement; + Expression argument; + { + Expression target = getQualifiedPropertyTarget(node); + if (target != null) { + DartType targetType = target.bestType; + if (targetType != null && targetType.element is ClassElement) { + targetElement = targetType.element as ClassElement; + argument = target.parent as Expression; + } else { + return; + } + } else { + ClassDeclaration enclosingClass = + node.getAncestor((node) => node is ClassDeclaration); + targetElement = enclosingClass != null ? + enclosingClass.element : + null; + argument = nameNode; + } + } + // should be argument of some invocation + ParameterElement parameterElement = argument.bestParameterElement; + if (parameterElement == null) { + return; + } + // should be parameter of function type + DartType parameterType = parameterElement.type; + if (parameterType is! FunctionType) { + return; + } + FunctionType functionType = parameterType as FunctionType; + // add proposal + if (targetElement != null) { + _addProposal_createFunction_method(targetElement, functionType); + } else { + _addProposal_createFunction_function(functionType); + } + } } void _addFix_createMissingOverrides(List missingOverrides) { - // TODO(scheglov) implement -// // sort by name -// missingOverrides.sort( -// (Element firstElement, Element secondElement) => -// ObjectUtils.compare(firstElement.displayName, secondElement.displayName)); -// // add elements -// ClassDeclaration targetClass = node.parent as ClassDeclaration; -// bool isFirst = true; -// for (ExecutableElement missingOverride in missingOverrides) { -// _addFix_createMissingOverrides_single( -// targetClass, -// missingOverride, -// isFirst); -// isFirst = false; -// } -// // add proposal -// _addFix( -// FixKind.CREATE_MISSING_OVERRIDES, -// [missingOverrides.length]); + // sort by name + missingOverrides.sort((Element firstElement, Element secondElement) { + return compareStrings( + firstElement.displayName, + secondElement.displayName); + }); + // TODO + ClassDeclaration targetClass = node.parent as ClassDeclaration; + int insertOffset = targetClass.end - 1; + SourceBuilder sb = new SourceBuilder(file, insertOffset); + // add elements + bool isFirst = true; + for (ExecutableElement missingOverride in missingOverrides) { + if (!isFirst || !targetClass.members.isEmpty) { + sb.append(eol); + } + _addFix_createMissingOverrides_single(sb, targetClass, missingOverride); + isFirst = false; + } + // add proposal + endPosition = new Position(file, insertOffset, 0); + _insertBuilder(sb); + _addFix(FixKind.CREATE_MISSING_OVERRIDES, [missingOverrides.length]); } - void _addFix_createMissingOverrides_single(ClassDeclaration targetClass, - ExecutableElement missingOverride, bool isFirst) { - // TODO(scheglov) implement -// // prepare environment -// String eol = utils.endOfLine; -// String prefix = utils.getIndent(1); -// String prefix2 = utils.getIndent(2); -// int insertOffset = targetClass.end - 1; -// // prepare source -// JavaStringBuilder sb = new JavaStringBuilder(); -// // may be empty line -// if (!isFirst || !targetClass.members.isEmpty) { -// sb.append(eol); -// } -// // may be property -// ElementKind elementKind = missingOverride.kind; -// bool isGetter = elementKind == ElementKind.GETTER; -// bool isSetter = elementKind == ElementKind.SETTER; -// bool isMethod = elementKind == ElementKind.METHOD; -// bool isOperator = isMethod && (missingOverride as MethodElement).isOperator; -// sb.append(prefix); -// if (isGetter) { -// sb.append("// TODO: implement ${missingOverride.displayName}"); -// sb.append(eol); -// sb.append(prefix); -// } -// // @override -// { -// sb.append("@override"); -// sb.append(eol); -// sb.append(prefix); -// } -// // return type -// _appendType(sb, missingOverride.type.returnType); -// if (isGetter) { -// sb.append("get "); -// } else if (isSetter) { -// sb.append("set "); -// } else if (isOperator) { -// sb.append("operator "); -// } -// // name -// sb.append(missingOverride.displayName); -// // parameters + body -// if (isGetter) { -// sb.append(" => null;"); -// } else if (isMethod || isSetter) { -// List parameters = missingOverride.parameters; -// _appendParameters(sb, parameters); -// sb.append(" {"); -// // TO-DO -// sb.append(eol); -// sb.append(prefix2); -// if (isMethod) { -// sb.append("// TODO: implement ${missingOverride.displayName}"); -// } else { -// sb.append("// TODO: implement ${missingOverride.displayName}"); -// } -// sb.append(eol); -// // close method -// sb.append(prefix); -// sb.append("}"); -// } -// sb.append(eol); -// // done -// _addInsertEdit(insertOffset, sb.toString()); -// // maybe set end range -// if (_endRange == null) { -// _endRange = SourceRangeFactory.rangeStartLength(insertOffset, 0); -// } + void _addFix_createMissingOverrides_single(SourceBuilder sb, + ClassDeclaration targetClass, ExecutableElement missingOverride) { + // prepare environment + String prefix = utils.getIndent(1); + String prefix2 = utils.getIndent(2); + // may be property + ElementKind elementKind = missingOverride.kind; + bool isGetter = elementKind == ElementKind.GETTER; + bool isSetter = elementKind == ElementKind.SETTER; + bool isMethod = elementKind == ElementKind.METHOD; + bool isOperator = isMethod && (missingOverride as MethodElement).isOperator; + sb.append(prefix); + if (isGetter) { + sb.append('// TODO: implement ${missingOverride.displayName}'); + sb.append(eol); + sb.append(prefix); + } + // @override + { + sb.append('@override'); + sb.append(eol); + sb.append(prefix); + } + // return type + _appendType(sb, missingOverride.type.returnType); + if (isGetter) { + sb.append('get '); + } else if (isSetter) { + sb.append('set '); + } else if (isOperator) { + sb.append('operator '); + } + // name + sb.append(missingOverride.displayName); + // parameters + body + if (isGetter) { + sb.append(' => null;'); + } else { + List parameters = missingOverride.parameters; + _appendParameters(sb, parameters, _getDefaultValueMap(parameters)); + sb.append(' {'); + // TO-DO + sb.append(eol); + sb.append(prefix2); + sb.append('// TODO: implement ${missingOverride.displayName}'); + sb.append(eol); + // close method + sb.append(prefix); + sb.append('}'); + } + sb.append(eol); } void _addFix_createNoSuchMethod() { - // TODO(scheglov) implement -// ClassDeclaration targetClass = node.parent as ClassDeclaration; -// // prepare environment -// String eol = utils.endOfLine; -// String prefix = utils.getIndent(1); -// int insertOffset = targetClass.end - 1; -// // prepare source -// SourceBuilder sb = new SourceBuilder.con1(insertOffset); -// { -// // insert empty line before existing member -// if (!targetClass.members.isEmpty) { -// sb.append(eol); -// } -// // append method -// sb.append(prefix); -// sb.append( -// "noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);"); -// sb.append(eol); -// } -// // done -// _addInsertEdit3(sb); -// _endRange = SourceRangeFactory.rangeStartLength(insertOffset, 0); -// // add proposal -// _addFix(FixKind.CREATE_NO_SUCH_METHOD, []); + ClassDeclaration targetClass = node.parent as ClassDeclaration; + // prepare environment + String prefix = utils.getIndent(1); + int insertOffset = targetClass.end - 1; + // prepare source + SourceBuilder sb = new SourceBuilder(file, insertOffset); + { + // insert empty line before existing member + if (!targetClass.members.isEmpty) { + sb.append(eol); + } + // append method + sb.append(prefix); + sb.append( + "noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);"); + sb.append(eol); + } + // done + _insertBuilder(sb); + endPosition = new Position(file, insertOffset, 0); + // add proposal + _addFix(FixKind.CREATE_NO_SUCH_METHOD, []); } + void _addFix_createPart() { // TODO(scheglov) implement // if (node is SimpleStringLiteral && node.parent is PartDirective) { @@ -712,7 +707,6 @@ class FixProcessor { // // prepare new source // String source; // { -// String eol = utils.endOfLine; // String libraryName = _unitLibraryElement.displayName; // source = "part of ${libraryName};${eol}${eol}"; // } @@ -737,7 +731,6 @@ class FixProcessor { // String prefix; // String suffix; // { -// String eol = utils.endOfLine; // // if no directives // prefix = ""; // suffix = eol; @@ -1077,7 +1070,6 @@ class FixProcessor { // return; // } // // prepare environment -// String eol = utils.endOfLine; // int insertOffset; // String sourcePrefix; // AstNode enclosingMember = @@ -1151,213 +1143,80 @@ class FixProcessor { } void _addFix_undefinedMethod_create() { - // TODO(scheglov) implement -// if (node is SimpleIdentifier && node.parent is MethodInvocation) { -// String name = (node as SimpleIdentifier).name; -// MethodInvocation invocation = node.parent as MethodInvocation; -// // prepare environment -// String eol = utils.endOfLine; -// Source targetSource; -// String prefix; -// int insertOffset; -// String sourcePrefix; -// String sourceSuffix; -// bool staticModifier = false; -// Expression target = invocation.realTarget; -// if (target == null) { -// targetSource = _source; -// ClassMember enclosingMember = -// node.getAncestor((node) => node is ClassMember); -// staticModifier = _inStaticMemberContext2(enclosingMember); -// prefix = utils.getNodePrefix(enclosingMember); -// insertOffset = enclosingMember.end; -// sourcePrefix = "${eol}${prefix}${eol}"; -// sourceSuffix = ""; -// } else { -// // prepare target interface type -// DartType targetType = target.bestType; -// if (targetType is! InterfaceType) { -// return; -// } -// ClassElement targetElement = targetType.element as ClassElement; -// targetSource = targetElement.source; -// // may be static -// if (target is Identifier) { -// staticModifier = target.bestElement.kind == ElementKind.CLASS; -// } -// // prepare insert offset -// ClassDeclaration targetClass = targetElement.node; -// prefix = " "; -// insertOffset = targetClass.end - 1; -// if (targetClass.members.isEmpty) { -// sourcePrefix = ""; -// } else { -// sourcePrefix = "${prefix}${eol}"; -// } -// sourceSuffix = eol; -// } -// // build method source -// SourceBuilder sb = new SourceBuilder.con1(insertOffset); -// { -// sb.append(sourcePrefix); -// sb.append(prefix); -// // may be "static" -// if (staticModifier) { -// sb.append("static "); -// } -// // may be return type -// { -// DartType type = -// _addFix_undefinedMethod_create_getReturnType(invocation); -// if (type != null) { -// String typeSource = utils.getTypeSource2(type); -// if (typeSource != "dynamic") { -// sb.startPosition("RETURN_TYPE"); -// sb.append(typeSource); -// sb.endPosition(); -// sb.append(" "); -// } -// } -// } -// // append name -// { -// sb.startPosition("NAME"); -// sb.append(name); -// sb.endPosition(); -// } -// _addFix_undefinedMethod_create_parameters(sb, invocation.argumentList); -// sb.append(") {${eol}${prefix}}"); -// sb.append(sourceSuffix); -// } -// // insert source -// _addInsertEdit(insertOffset, sb.toString()); -// // add linked positions -// if (targetSource == _source) { -// _addLinkedPosition("NAME", sb, SourceRangeFactory.rangeNode(node)); -// } -// _addLinkedPositions(sb); -// // add proposal -// _addUnitCorrectionProposal2( -// targetSource, -// FixKind.CREATE_METHOD, -// [name]); -// } - } - - /** - * @return the possible return [Type], may be null if can not be identified. - */ - DartType - _addFix_undefinedMethod_create_getReturnType(MethodInvocation invocation) { - // TODO(scheglov) implement -// AstNode parent = invocation.parent; -// // myFunction(); -// if (parent is ExpressionStatement) { -// return VoidTypeImpl.instance; -// } -// // return myFunction(); -// if (parent is ReturnStatement) { -// ExecutableElement executable = -// CorrectionUtils.getEnclosingExecutableElement(invocation); -// return executable != null ? executable.returnType : null; -// } -// // int v = myFunction(); -// if (parent is VariableDeclaration) { -// VariableDeclaration variableDeclaration = parent; -// if (identical(variableDeclaration.initializer, invocation)) { -// VariableElement variableElement = variableDeclaration.element; -// if (variableElement != null) { -// return variableElement.type; -// } -// } -// } -// // v = myFunction(); -// if (parent is AssignmentExpression) { -// AssignmentExpression assignment = parent; -// if (identical(assignment.rightHandSide, invocation)) { -// if (assignment.operator.type == TokenType.EQ) { -// // v = myFunction(); -// Expression lhs = assignment.leftHandSide; -// if (lhs != null) { -// return lhs.bestType; -// } -// } else { -// // v += myFunction(); -// MethodElement method = assignment.bestElement; -// if (method != null) { -// List parameters = method.parameters; -// if (parameters.length == 1) { -// return parameters[0].type; -// } -// } -// } -// } -// } -// // v + myFunction(); -// if (parent is BinaryExpression) { -// BinaryExpression binary = parent; -// MethodElement method = binary.bestElement; -// if (method != null) { -// if (identical(binary.rightOperand, invocation)) { -// List parameters = method.parameters; -// return parameters.length == 1 ? parameters[0].type : null; -// } -// } -// } -// // foo( myFunction() ); -// if (parent is ArgumentList) { -// ParameterElement parameter = invocation.bestParameterElement; -// return parameter != null ? parameter.type : null; -// } -// // bool -// { -// // assert( myFunction() ); -// if (parent is AssertStatement) { -// AssertStatement statement = parent; -// if (identical(statement.condition, invocation)) { -// return coreTypeBool; -// } -// } -// // if ( myFunction() ) {} -// if (parent is IfStatement) { -// IfStatement statement = parent; -// if (identical(statement.condition, invocation)) { -// return coreTypeBool; -// } -// } -// // while ( myFunction() ) {} -// if (parent is WhileStatement) { -// WhileStatement statement = parent; -// if (identical(statement.condition, invocation)) { -// return coreTypeBool; -// } -// } -// // do {} while ( myFunction() ); -// if (parent is DoStatement) { -// DoStatement statement = parent; -// if (identical(statement.condition, invocation)) { -// return coreTypeBool; -// } -// } -// // !myFunction() -// if (parent is PrefixExpression) { -// PrefixExpression prefixExpression = parent; -// if (prefixExpression.operator.type == TokenType.BANG) { -// return coreTypeBool; -// } -// } -// // binary expression '&&' or '||' -// if (parent is BinaryExpression) { -// BinaryExpression binaryExpression = parent; -// TokenType operatorType = binaryExpression.operator.type; -// if (operatorType == TokenType.AMPERSAND_AMPERSAND || -// operatorType == TokenType.BAR_BAR) { -// return coreTypeBool; -// } -// } -// } - // we don't know - return null; + if (node is SimpleIdentifier && node.parent is MethodInvocation) { + String name = (node as SimpleIdentifier).name; + MethodInvocation invocation = node.parent as MethodInvocation; + // prepare environment + Source targetSource; + String prefix; + int insertOffset; + String sourcePrefix; + String sourceSuffix; + bool staticModifier = false; + Expression target = invocation.realTarget; + if (target == null) { + targetSource = source; + ClassMember enclosingMember = + node.getAncestor((node) => node is ClassMember); + staticModifier = _inStaticContext(); + prefix = utils.getNodePrefix(enclosingMember); + insertOffset = enclosingMember.end; + sourcePrefix = "${eol}${prefix}${eol}"; + sourceSuffix = ""; + } else { + // prepare target interface type + DartType targetType = target.bestType; + if (targetType is! InterfaceType) { + return; + } + ClassElement targetElement = targetType.element as ClassElement; + targetSource = targetElement.source; + // may be static + if (target is Identifier) { + staticModifier = target.bestElement.kind == ElementKind.CLASS; + } + // prepare insert offset + ClassDeclaration targetClass = targetElement.node; + prefix = " "; + insertOffset = targetClass.end - 1; + if (targetClass.members.isEmpty) { + sourcePrefix = ""; + } else { + sourcePrefix = eol; + } + sourceSuffix = eol; + } + String targetFile = targetSource.fullName; + // build method source + SourceBuilder sb = new SourceBuilder(targetFile, insertOffset); + { + sb.append(sourcePrefix); + sb.append(prefix); + // maybe "static" + if (staticModifier) { + sb.append("static "); + } + // append return type + _appendType(sb, _inferReturnType(invocation), 'RETURN_TYPE'); + // append name + { + sb.startPosition("NAME"); + sb.append(name); + sb.endPosition(); + } + _addFix_undefinedMethod_create_parameters(sb, invocation.argumentList); + sb.append(") {${eol}${prefix}}"); + sb.append(sourceSuffix); + } + // insert source + _insertBuilder(sb); + // add linked positions + if (targetSource == source) { + _addLinkedPosition3('NAME', sb, rf.rangeNode(node)); + } + // add proposal + _addFix(FixKind.CREATE_METHOD, [name], fixFile: targetFile); + } } void _addFix_undefinedMethod_create_parameters(SourceBuilder sb, @@ -1516,6 +1375,143 @@ class FixProcessor { group.add(position); } + /** + * Adds a single linked position to [groupId]. + */ + void _addLinkedPosition3(String groupId, SourceBuilder sb, + SourceRange range) { + if (sb.offset < range.offset) { + int delta = sb.length; + range = range.getTranslated(delta); + } + _addLinkedPosition(groupId, range); + } + + /** + * Prepares proposal for creating function corresponding to the given [FunctionType]. + */ + void _addProposal_createFunction(FunctionType functionType, String name, + Source targetSource, int insertOffset, bool isStatic, String prefix, + String sourcePrefix, String sourceSuffix) { + // build method source + String targetFile = targetSource.fullName; + SourceBuilder sb = new SourceBuilder(targetFile, insertOffset); + { + sb.append(sourcePrefix); + sb.append(prefix); + // may be static + if (isStatic) { + sb.append("static "); + } + // append return type + _appendType(sb, functionType.returnType, 'RETURN_TYPE'); + // append name + { + sb.startPosition("NAME"); + sb.append(name); + sb.endPosition(); + } + // append parameters + sb.append("("); + List parameters = functionType.parameters; + for (int i = 0; i < parameters.length; i++) { + ParameterElement parameter = parameters[i]; + // append separator + if (i != 0) { + sb.append(", "); + } + // append type name + DartType type = parameter.type; + if (!type.isDynamic) { + String typeSource = utils.getTypeSource(type); + { + sb.startPosition("TYPE${i}"); + sb.append(typeSource); + _addSuperTypeProposals(sb, new Set(), type); + sb.endPosition(); + } + sb.append(" "); + } + // append parameter name + { + sb.startPosition("ARG${i}"); + sb.append(parameter.displayName); + sb.endPosition(); + } + } + sb.append(")"); + // close method + sb.append(" {${eol}${prefix}}"); + sb.append(sourceSuffix); + } + // insert source + _insertBuilder(sb); + // add linked positions + if (targetSource == source) { + _addLinkedPosition3("NAME", sb, rf.rangeNode(node)); + } + } + + /** + * Adds proposal for creating method corresponding to the given [FunctionType] in the given + * [ClassElement]. + */ + void _addProposal_createFunction_function(FunctionType functionType) { + String name = (node as SimpleIdentifier).name; + // prepare environment + int insertOffset = unit.end; + // prepare prefix + String prefix = ""; + String sourcePrefix = "${eol}"; + String sourceSuffix = eol; + _addProposal_createFunction( + functionType, + name, + source, + insertOffset, + false, + prefix, + sourcePrefix, + sourceSuffix); + // add proposal + _addFix(FixKind.CREATE_FUNCTION, [name], fixFile: file); + } + + /** + * Adds proposal for creating method corresponding to the given [FunctionType] in the given + * [ClassElement]. + */ + void _addProposal_createFunction_method(ClassElement targetClassElement, + FunctionType functionType) { + String name = (node as SimpleIdentifier).name; + // prepare environment + Source targetSource = targetClassElement.source; + String targetFile = targetSource.fullName; + // prepare insert offset + ClassDeclaration targetClassNode = targetClassElement.node; + int insertOffset = targetClassNode.end - 1; + // prepare prefix + String prefix = " "; + String sourcePrefix; + if (targetClassNode.members.isEmpty) { + sourcePrefix = ""; + } else { + sourcePrefix = eol; + } + String sourceSuffix = eol; + _addProposal_createFunction( + functionType, + name, + targetSource, + insertOffset, + _inStaticContext(), + prefix, + sourcePrefix, + sourceSuffix); + // add proposal + _addFix(FixKind.CREATE_METHOD, [name], fixFile: targetFile); + } + /** * Adds a new [Edit] to [edits]. */ @@ -1531,20 +1527,20 @@ class FixProcessor { edits.add(edit); } - void _appendParameterSource(StringBuffer sb, DartType type, String name) { + void _appendParameterSource(SourceBuilder sb, DartType type, String name) { String parameterSource = utils.getParameterSource(type, name); - sb.write(parameterSource); + sb.append(parameterSource); } - void _appendParameters(StringBuffer sb, List parameters, + void _appendParameters(SourceBuilder sb, List parameters, Map defaultValueMap) { - sb.write("("); + sb.append("("); bool firstParameter = true; bool sawNamed = false; bool sawPositional = false; for (ParameterElement parameter in parameters) { if (!firstParameter) { - sb.write(", "); + sb.append(", "); } else { firstParameter = false; } @@ -1552,13 +1548,13 @@ class FixProcessor { ParameterKind parameterKind = parameter.parameterKind; if (parameterKind == ParameterKind.NAMED) { if (!sawNamed) { - sb.write("{"); + sb.append("{"); sawNamed = true; } } if (parameterKind == ParameterKind.POSITIONAL) { if (!sawPositional) { - sb.write("["); + sb.append("["); sawPositional = true; } } @@ -1569,22 +1565,224 @@ class FixProcessor { String defaultSource = defaultValueMap[parameter]; if (defaultSource != null) { if (sawPositional) { - sb.write(" = "); + sb.append(" = "); } else { - sb.write(": "); + sb.append(": "); } - sb.write(defaultSource); + sb.append(defaultSource); } } } // close parameters if (sawNamed) { - sb.write("}"); + sb.append("}"); } if (sawPositional) { - sb.write("]"); + sb.append("]"); } - sb.write(")"); + sb.append(")"); + } + + void _appendType(SourceBuilder sb, DartType type, [String groupId]) { + if (type != null && !type.isDynamic) { + String typeSource = utils.getTypeSource(type); + if (groupId != null) { + sb.startPosition(groupId); + sb.append(typeSource); + sb.endPosition(); + } else { + sb.append(typeSource); + } + sb.append(' '); + } + } + + /** + * @return the string to display as the name of the given constructor in a proposal name. + */ + String _getConstructorProposalName(ConstructorElement constructor) { + SourceBuilder proposalNameBuffer = new SourceBuilder.buffer(); + proposalNameBuffer.append("super"); + // may be named + String constructorName = constructor.displayName; + if (!constructorName.isEmpty) { + proposalNameBuffer.append("."); + proposalNameBuffer.append(constructorName); + } + // parameters + _appendParameters(proposalNameBuffer, constructor.parameters, null); + // done + return proposalNameBuffer.toString(); + } + + /** + * Returns the [Type] with given name from the `dart:core` library. + */ + DartType _getCoreType(String name) { + List libraries = unitLibraryElement.importedLibraries; + for (LibraryElement library in libraries) { + if (library.isDartCore) { + ClassElement classElement = library.getType(name); + if (classElement != null) { + return classElement.type; + } + return null; + } + } + return null; + } + + Map + _getDefaultValueMap(List parameters) { + Map defaultSourceMap = {}; + Map sourceContentMap = {}; + for (ParameterElement parameter in parameters) { + SourceRange valueRange = parameter.defaultValueRange; + if (valueRange != null) { + Source source = parameter.source; + String sourceContent = sourceContentMap[source]; + if (sourceContent == null) { + sourceContent = getSourceContent(parameter.context, source); + sourceContentMap[source] = sourceContent; + } + String valueSource = + sourceContent.substring(valueRange.offset, valueRange.end); + defaultSourceMap[parameter] = valueSource; + } + } + return defaultSourceMap; + } + + /** + * Returns `true` if [node] is in static context. + */ + bool _inStaticContext() { + // constructor initializer cannot reference "this" + if (node.getAncestor((node) => node is ConstructorInitializer) != null) { + return true; + } + // field initializer cannot reference "this" + if (node.getAncestor((node) => node is FieldDeclaration) != null) { + return true; + } + // static method + MethodDeclaration method = node.getAncestor((node) { + return node is MethodDeclaration; + }); + return method != null && method.isStatic; + } + + /** + * Returns a possible return [Type], may be `null` if cannot be inferred. + */ + DartType _inferReturnType(MethodInvocation invocation) { + AstNode parent = invocation.parent; + // myFunction(); + if (parent is ExpressionStatement) { + return VoidTypeImpl.instance; + } + // return myFunction(); + if (parent is ReturnStatement) { + ExecutableElement executable = getEnclosingExecutableElement(invocation); + return executable != null ? executable.returnType : null; + } + // int v = myFunction(); + if (parent is VariableDeclaration) { + VariableDeclaration variableDeclaration = parent; + if (identical(variableDeclaration.initializer, invocation)) { + VariableElement variableElement = variableDeclaration.element; + if (variableElement != null) { + return variableElement.type; + } + } + } + // v = myFunction(); + if (parent is AssignmentExpression) { + AssignmentExpression assignment = parent; + if (identical(assignment.rightHandSide, invocation)) { + if (assignment.operator.type == TokenType.EQ) { + // v = myFunction(); + Expression lhs = assignment.leftHandSide; + if (lhs != null) { + return lhs.bestType; + } + } else { + // v += myFunction(); + MethodElement method = assignment.bestElement; + if (method != null) { + List parameters = method.parameters; + if (parameters.length == 1) { + return parameters[0].type; + } + } + } + } + } + // v + myFunction(); + if (parent is BinaryExpression) { + BinaryExpression binary = parent; + MethodElement method = binary.bestElement; + if (method != null) { + if (identical(binary.rightOperand, invocation)) { + List parameters = method.parameters; + return parameters.length == 1 ? parameters[0].type : null; + } + } + } + // foo( myFunction() ); + if (parent is ArgumentList) { + ParameterElement parameter = invocation.bestParameterElement; + return parameter != null ? parameter.type : null; + } + // bool + { + // assert( myFunction() ); + if (parent is AssertStatement) { + AssertStatement statement = parent; + if (identical(statement.condition, invocation)) { + return coreTypeBool; + } + } + // if ( myFunction() ) {} + if (parent is IfStatement) { + IfStatement statement = parent; + if (identical(statement.condition, invocation)) { + return coreTypeBool; + } + } + // while ( myFunction() ) {} + if (parent is WhileStatement) { + WhileStatement statement = parent; + if (identical(statement.condition, invocation)) { + return coreTypeBool; + } + } + // do {} while ( myFunction() ); + if (parent is DoStatement) { + DoStatement statement = parent; + if (identical(statement.condition, invocation)) { + return coreTypeBool; + } + } + // !myFunction() + if (parent is PrefixExpression) { + PrefixExpression prefixExpression = parent; + if (prefixExpression.operator.type == TokenType.BANG) { + return coreTypeBool; + } + } + // binary expression '&&' or '||' + if (parent is BinaryExpression) { + BinaryExpression binaryExpression = parent; + TokenType operatorType = binaryExpression.operator.type; + if (operatorType == TokenType.AMPERSAND_AMPERSAND || + operatorType == TokenType.BAR_BAR) { + return coreTypeBool; + } + } + } + // we don't know + return null; } // void _addLinkedPositionProposal(String group, @@ -1597,23 +1795,20 @@ class FixProcessor { // nodeProposals.add(proposal); // } - /** - * @return the string to display as the name of the given constructor in a proposal name. - */ - String _getConstructorProposalName(ConstructorElement constructor) { - StringBuffer proposalNameBuffer = new StringBuffer(); - proposalNameBuffer.write("super"); - // may be named - String constructorName = constructor.displayName; - if (!constructorName.isEmpty) { - proposalNameBuffer.write("."); - proposalNameBuffer.write(constructorName); - } - // parameters - _appendParameters(proposalNameBuffer, constructor.parameters, null); - // done - return proposalNameBuffer.toString(); - } +// /** +// * Returns `true` if the given [ClassMember] is a part of a static method or +// * a field initializer. +// */ +// bool _inStaticMemberContext2(ClassMember member) { +// if (member is MethodDeclaration) { +// return member.isStatic; +// } +// // field initializer cannot reference "this" +// if (member is FieldDeclaration) { +// return true; +// } +// return false; +// } /** * Inserts the given [SourceBuilder] at its offset. @@ -1629,8 +1824,8 @@ class FixProcessor { }); } - QuickFixProcessorImpl_NewConstructorLocation - _prepareNewConstructorLocation(ClassDeclaration classDeclaration, String eol) { + _ConstructorLocation + _prepareNewConstructorLocation(ClassDeclaration classDeclaration) { List members = classDeclaration.members; // find the last field/constructor ClassMember lastFieldOrConstructor = null; @@ -1643,14 +1838,14 @@ class FixProcessor { } // after the field/constructor if (lastFieldOrConstructor != null) { - return new QuickFixProcessorImpl_NewConstructorLocation( + return new _ConstructorLocation( "${eol}${eol}", lastFieldOrConstructor.end, ""); } // at the beginning of the class String suffix = members.isEmpty ? "" : eol; - return new QuickFixProcessorImpl_NewConstructorLocation( + return new _ConstructorLocation( eol, classDeclaration.leftBracket.end, suffix); @@ -1728,11 +1923,10 @@ class FixProcessor { * * TODO(scheglov) rename */ -class QuickFixProcessorImpl_NewConstructorLocation { +class _ConstructorLocation { final String _prefix; final int _offset; final String _suffix; - QuickFixProcessorImpl_NewConstructorLocation(this._prefix, this._offset, - this._suffix); + _ConstructorLocation(this._prefix, this._offset, this._suffix); } diff --git a/pkg/analysis_services/lib/src/correction/source_buffer.dart b/pkg/analysis_services/lib/src/correction/source_buffer.dart index fb9e2048e8f..04b3a2bf1e2 100644 --- a/pkg/analysis_services/lib/src/correction/source_buffer.dart +++ b/pkg/analysis_services/lib/src/correction/source_buffer.dart @@ -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(); diff --git a/pkg/analysis_services/lib/src/correction/strings.dart b/pkg/analysis_services/lib/src/correction/strings.dart index 14887cc7395..02acad66346 100644 --- a/pkg/analysis_services/lib/src/correction/strings.dart +++ b/pkg/analysis_services/lib/src/correction/strings.dart @@ -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++) { diff --git a/pkg/analysis_services/lib/src/correction/util.dart b/pkg/analysis_services/lib/src/correction/util.dart index e868a0fd2c3..b8ccbba1f3d 100644 --- a/pkg/analysis_services/lib/src/correction/util.dart +++ b/pkg/analysis_services/lib/src/correction/util.dart @@ -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 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]. */ diff --git a/pkg/analysis_services/test/correction/fix_test.dart b/pkg/analysis_services/test/correction/fix_test.dart index d0e4f12e186..f5f688f6888 100644 --- a/pkg/analysis_services/test/correction/fix_test.dart +++ b/pkg/analysis_services/test/correction/fix_test.dart @@ -59,7 +59,7 @@ class FixProcessorTest extends AbstractSingleUnitTest { void assertNoFix(FixKind kind) { AnalysisError error = _findErrorToFix(); - List fixes = computeFixes(searchEngine, testFile, testUnit, error); + List 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 p3, [int p4]); +} +class B extends A { + int existingField; + + void existingMethod() {} +} +'''); + assertHasFix(FixKind.CREATE_CONSTRUCTOR_SUPER, ''' +class A { + A(p1, int p2, List p3, [int p4]); +} +class B extends A { + int existingField; + + B(p1, int p2, List 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 { +} + +abstract class IterableMixin { + Iterator get iterator; +} + +class Test extends IterableMixin { +} +'''); + assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, ''' +class Iterator { +} + +abstract class IterableMixin { + Iterator get iterator; +} + +class Test extends IterableMixin { + // TODO: implement iterator + @override + Iterator 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> g(aaa.Future p); +} + +class B extends A { +} +'''); + assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, ''' +import 'dart:async' as aaa; +abstract class A { + Map> g(aaa.Future p); +} + +class B extends A { + @override + Map> 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> 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> 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> 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 fixes = computeFixes(searchEngine, testFile, testUnit, error); + List fixes = computeFixes(searchEngine, testUnit, error); for (Fix fix in fixes) { if (fix.kind == kind) { return fix;