/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.services.internal.correction; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.dart.engine.ast.ArgumentList; import com.google.dart.engine.ast.AsExpression; import com.google.dart.engine.ast.AssertStatement; import com.google.dart.engine.ast.AssignmentExpression; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.BinaryExpression; import com.google.dart.engine.ast.ClassDeclaration; import com.google.dart.engine.ast.ClassMember; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.CompilationUnitMember; import com.google.dart.engine.ast.ConstructorDeclaration; import com.google.dart.engine.ast.ConstructorInitializer; import com.google.dart.engine.ast.ConstructorName; import com.google.dart.engine.ast.Directive; import com.google.dart.engine.ast.DoStatement; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.ExpressionStatement; import com.google.dart.engine.ast.FieldDeclaration; import com.google.dart.engine.ast.FunctionBody; import com.google.dart.engine.ast.Identifier; import com.google.dart.engine.ast.IfStatement; import com.google.dart.engine.ast.ImportDirective; import com.google.dart.engine.ast.InstanceCreationExpression; import com.google.dart.engine.ast.IsExpression; import com.google.dart.engine.ast.LibraryDirective; import com.google.dart.engine.ast.MethodDeclaration; import com.google.dart.engine.ast.MethodInvocation; import com.google.dart.engine.ast.NamespaceDirective; import com.google.dart.engine.ast.ParenthesizedExpression; import com.google.dart.engine.ast.PartDirective; import com.google.dart.engine.ast.PrefixExpression; import com.google.dart.engine.ast.PrefixedIdentifier; import com.google.dart.engine.ast.ReturnStatement; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SimpleStringLiteral; import com.google.dart.engine.ast.TypeName; import com.google.dart.engine.ast.VariableDeclaration; import com.google.dart.engine.ast.WhileStatement; import com.google.dart.engine.ast.visitor.NodeLocator; import com.google.dart.engine.context.AnalysisContext; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.CompilationUnitElement; import com.google.dart.engine.element.ConstructorElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ElementKind; import com.google.dart.engine.element.ExecutableElement; import com.google.dart.engine.element.FunctionElement; import com.google.dart.engine.element.ImportElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.element.MethodElement; import com.google.dart.engine.element.NamespaceCombinator; import com.google.dart.engine.element.ParameterElement; import com.google.dart.engine.element.PrefixElement; import com.google.dart.engine.element.PropertyAccessorElement; import com.google.dart.engine.element.ShowElementCombinator; import com.google.dart.engine.element.VariableElement; import com.google.dart.engine.element.visitor.RecursiveElementVisitor; import com.google.dart.engine.error.AnalysisError; import com.google.dart.engine.error.AnalysisErrorWithProperties; import com.google.dart.engine.error.CompileTimeErrorCode; import com.google.dart.engine.error.ErrorCode; import com.google.dart.engine.error.ErrorProperty; import com.google.dart.engine.error.HintCode; import com.google.dart.engine.error.StaticTypeWarningCode; import com.google.dart.engine.error.StaticWarningCode; import com.google.dart.engine.internal.type.VoidTypeImpl; import com.google.dart.engine.parser.ParserErrorCode; import com.google.dart.engine.scanner.TokenType; import com.google.dart.engine.sdk.DartSdk; import com.google.dart.engine.sdk.SdkLibrary; import com.google.dart.engine.services.assist.AssistContext; import com.google.dart.engine.services.change.Edit; import com.google.dart.engine.services.change.SourceChange; import com.google.dart.engine.services.correction.AddDependencyCorrectionProposal; import com.google.dart.engine.services.correction.CorrectionImage; import com.google.dart.engine.services.correction.CorrectionKind; import com.google.dart.engine.services.correction.CorrectionProposal; import com.google.dart.engine.services.correction.CreateFileCorrectionProposal; import com.google.dart.engine.services.correction.LinkedPositionProposal; import com.google.dart.engine.services.correction.QuickFixProcessor; import com.google.dart.engine.services.correction.SourceCorrectionProposal; import com.google.dart.engine.services.internal.correction.CorrectionUtils.InsertDesc; import com.google.dart.engine.services.util.HierarchyUtils; import com.google.dart.engine.source.FileBasedSource; import com.google.dart.engine.source.Source; import com.google.dart.engine.source.SourceFactory; import com.google.dart.engine.type.FunctionType; import com.google.dart.engine.type.InterfaceType; import com.google.dart.engine.type.Type; import com.google.dart.engine.utilities.dart.ParameterKind; import com.google.dart.engine.utilities.instrumentation.Instrumentation; import com.google.dart.engine.utilities.instrumentation.InstrumentationBuilder; import com.google.dart.engine.utilities.source.SourceRange; import com.google.dart.engine.utilities.source.SourceRangeFactory; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndEnd; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndStart; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeError; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeNode; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeShowCombinator; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartLength; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartStart; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeToken; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Implementation of {@link QuickFixProcessor}. */ public class QuickFixProcessorImpl implements QuickFixProcessor { /** * Helper for finding {@link Element} with name closest to the given. */ private static class ClosestElementFinder { private final String targetName; private final Predicate<Element> predicate; Element element = null; int distance = Integer.MAX_VALUE; public ClosestElementFinder(String targetName, Predicate<Element> predicate) { this.targetName = targetName; this.predicate = predicate; } void update(Element element) { if (predicate.apply(element)) { int memberDistance = StringUtils.getLevenshteinDistance(element.getName(), targetName); if (memberDistance < distance) { this.element = element; this.distance = memberDistance; } } } void update(Iterable<? extends Element> elements) { for (Element element : elements) { update(element); } } } /** * Described location for newly created {@link ConstructorDeclaration}. */ private static class NewConstructorLocation { final String prefix; final int offset; final String suffix; public NewConstructorLocation(String prefix, int offset, String suffix) { this.prefix = prefix; this.offset = offset; this.suffix = suffix; } } private static final ErrorCode[] FIXABLE_ERROR_CODES = { CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT, CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT, CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT, CompileTimeErrorCode.URI_DOES_NOT_EXIST, // HintCode.DIVISION_OPTIMIZATION, HintCode.TYPE_CHECK_IS_NOT_NULL, HintCode.TYPE_CHECK_IS_NULL, HintCode.UNNECESSARY_CAST, HintCode.UNUSED_IMPORT, HintCode.UNDEFINED_METHOD, // ParserErrorCode.EXPECTED_TOKEN, ParserErrorCode.GETTER_WITH_PARAMETERS, // StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER, StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS, StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR, StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE, StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO, StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE, StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR, StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS, StaticWarningCode.UNDEFINED_CLASS, StaticWarningCode.UNDEFINED_CLASS_BOOLEAN, StaticWarningCode.UNDEFINED_IDENTIFIER, // StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION, StaticTypeWarningCode.UNDEFINED_FUNCTION, StaticTypeWarningCode.UNDEFINED_GETTER, StaticTypeWarningCode.UNDEFINED_METHOD}; private static final CorrectionProposal[] NO_PROPOSALS = {}; /** * @return the Java {@link File} which corresponds to the given {@link Source}, may be * {@code null} if cannot be determined. */ @VisibleForTesting public static File getSourceFile(Source source) { if (source instanceof FileBasedSource) { FileBasedSource fileBasedSource = (FileBasedSource) source; return new File(fileBasedSource.getFullName()).getAbsoluteFile(); } return null; } private static void addSuperTypeProposals(SourceBuilder sb, Set<Type> alreadyAdded, Type type) { if (type != null && !alreadyAdded.contains(type) && type.getElement() instanceof ClassElement) { alreadyAdded.add(type); ClassElement element = (ClassElement) type.getElement(); sb.addProposal(CorrectionImage.IMG_CORRECTION_CLASS, element.getName()); addSuperTypeProposals(sb, alreadyAdded, element.getSupertype()); for (InterfaceType interfaceType : element.getInterfaces()) { addSuperTypeProposals(sb, alreadyAdded, interfaceType); } } } /** * @return the {@link Edit} to remove {@link SourceRange}. */ private static Edit createRemoveEdit(SourceRange range) { return createReplaceEdit(range, ""); } /** * @return the {@link Edit} to replace {@link SourceRange} with "text". */ private static Edit createReplaceEdit(SourceRange range, String text) { return new Edit(range.getOffset(), range.getLength(), text); } /** * Attempts to convert the given absolute {@link File} to the "package" {@link URI}. * * @param context the {@link AnalysisContext} to work in. * @param file the absolute {@link File}, not null. * @return the "package" {@link URI}, may be {@code null}. */ private static URI findPackageUri(AnalysisContext context, File file) { Source fileSource = new FileBasedSource(file); return context.getSourceFactory().restoreUri(fileSource); } /** * @return the suggestions for given {@link Type} and {@link DartExpression}, not empty. */ private static String[] getArgumentNameSuggestions(Set<String> excluded, Type type, Expression expression, int index) { String[] suggestions = CorrectionUtils.getVariableNameSuggestions(type, expression, excluded); if (suggestions.length != 0) { return suggestions; } return new String[] {"arg" + index}; } /** * @return <code>true</code> if given {@link DartNode} could be type name. */ private static boolean mayBeTypeIdentifier(AstNode node) { if (node instanceof SimpleIdentifier) { AstNode parent = node.getParent(); if (parent instanceof TypeName) { return true; } if (parent instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) parent; return invocation.getRealTarget() == node; } if (parent instanceof PrefixedIdentifier) { PrefixedIdentifier prefixed = (PrefixedIdentifier) parent; return prefixed.getPrefix() == node; } } return false; } private final List<CorrectionProposal> proposals = Lists.newArrayList(); private final List<Edit> textEdits = Lists.newArrayList(); private AnalysisError problem; private Source source; private CompilationUnit unit; private LibraryElement unitLibraryElement; private File unitFile; private File unitLibraryFile; private File unitLibraryFolder; private AstNode node; private AstNode coveredNode; private int selectionOffset; private int selectionLength; private CorrectionUtils utils; private final Map<SourceRange, Edit> positionStopEdits = Maps.newHashMap(); private final Map<String, List<SourceRange>> linkedPositions = Maps.newHashMap(); private final Map<String, List<LinkedPositionProposal>> linkedPositionProposals = Maps.newHashMap(); private SourceRange endRange = null; @Override public CorrectionProposal[] computeProposals(AssistContext context, AnalysisError problem) throws Exception { if (context == null) { return NO_PROPOSALS; } if (problem == null) { return NO_PROPOSALS; } this.problem = problem; proposals.clear(); selectionOffset = problem.getOffset(); selectionLength = problem.getLength(); source = context.getSource(); unitFile = getSourceFile(source); unit = context.getCompilationUnit(); // prepare elements { CompilationUnitElement unitElement = unit.getElement(); if (unitElement == null) { return NO_PROPOSALS; } unitLibraryElement = unitElement.getLibrary(); if (unitLibraryElement == null) { return NO_PROPOSALS; } unitLibraryFile = getSourceFile(unitLibraryElement.getSource()); if (unitLibraryFile == null) { return NO_PROPOSALS; } unitLibraryFolder = unitLibraryFile.getParentFile(); } // prepare CorrectionUtils utils = new CorrectionUtils(unit); node = utils.findNode(selectionOffset); coveredNode = new NodeLocator(selectionOffset, selectionOffset + selectionLength).searchWithin(unit); // final InstrumentationBuilder instrumentation = Instrumentation.builder(this.getClass()); try { ErrorCode errorCode = problem.getErrorCode(); if (errorCode == CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE) { addFix_replaceWithConstInstanceCreation(); } if (errorCode == CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT) { addFix_createConstructorSuperExplicit(); } if (errorCode == CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT) { addFix_createConstructorSuperImplicit(); } if (errorCode == CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT) { addFix_createConstructorSuperExplicit(); } if (errorCode == CompileTimeErrorCode.URI_DOES_NOT_EXIST) { addFix_createPart(); addFix_addPackageDependency(); } if (errorCode == HintCode.DIVISION_OPTIMIZATION) { addFix_useEffectiveIntegerDivision(); } if (errorCode == HintCode.TYPE_CHECK_IS_NOT_NULL) { addFix_isNotNull(); } if (errorCode == HintCode.TYPE_CHECK_IS_NULL) { addFix_isNull(); } if (errorCode == HintCode.UNNECESSARY_CAST) { addFix_removeUnnecessaryCast(); } if (errorCode == HintCode.UNUSED_IMPORT) { addFix_removeUnusedImport(); } if (errorCode == ParserErrorCode.EXPECTED_TOKEN) { addFix_insertSemicolon(); } if (errorCode == ParserErrorCode.GETTER_WITH_PARAMETERS) { addFix_removeParameters_inGetterDeclaration(); } if (errorCode == StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER) { addFix_makeEnclosingClassAbstract(); } if (errorCode == StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS) { addFix_createConstructor_insteadOfSyntheticDefault(); } if (errorCode == StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR) { addFix_createConstructor_named(); } if (errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS) { // make class abstract addFix_makeEnclosingClassAbstract(); // implement methods AnalysisErrorWithProperties errorWithProperties = (AnalysisErrorWithProperties) problem; Object property = errorWithProperties.getProperty(ErrorProperty.UNIMPLEMENTED_METHODS); ExecutableElement[] missingOverrides = (ExecutableElement[]) property; addFix_createMissingOverrides(missingOverrides); addFix_createNoSuchMethod(); } if (errorCode == StaticWarningCode.UNDEFINED_CLASS) { addFix_importLibrary_withType(); addFix_createClass(); addFix_undefinedClass_useSimilar(); } if (errorCode == StaticWarningCode.UNDEFINED_CLASS_BOOLEAN) { addFix_boolInsteadOfBoolean(); } 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(); } if (errorCode == StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION) { addFix_removeParentheses_inGetterInvocation(); } if (errorCode == StaticTypeWarningCode.UNDEFINED_FUNCTION) { addFix_importLibrary_withFunction(); addFix_undefinedFunction_useSimilar(); 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(); } // clean-up resetProposalElements(); // write instrumentation instrumentation.metric("QuickFix-Offset", selectionOffset); instrumentation.metric("QuickFix-Length", selectionLength); instrumentation.metric("QuickFix-ProposalCount", proposals.size()); instrumentation.data("QuickFix-Source", utils.getText()); for (int index = 0; index < proposals.size(); index++) { instrumentation.data("QuickFix-Proposal-" + index, proposals.get(index).getName()); } // done return proposals.toArray(new CorrectionProposal[proposals.size()]); } finally { instrumentation.log(); } } @Override public ErrorCode[] getFixableErrorCodes() { return FIXABLE_ERROR_CODES; } @Override public boolean hasFix(AnalysisError problem) { ErrorCode errorCode = problem.getErrorCode(); // System.out.println(errorCode.getClass() + " " + errorCode); return errorCode == CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE || errorCode == CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT || errorCode == CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT || errorCode == CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT || errorCode == CompileTimeErrorCode.URI_DOES_NOT_EXIST || errorCode == HintCode.DIVISION_OPTIMIZATION || errorCode == HintCode.TYPE_CHECK_IS_NOT_NULL || errorCode == HintCode.TYPE_CHECK_IS_NULL || errorCode == HintCode.UNNECESSARY_CAST || errorCode == ParserErrorCode.EXPECTED_TOKEN || errorCode == HintCode.UNUSED_IMPORT || errorCode == HintCode.UNDEFINED_METHOD || errorCode == ParserErrorCode.GETTER_WITH_PARAMETERS || errorCode == StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER || errorCode == StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS || errorCode == StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR || errorCode == StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS || errorCode == StaticWarningCode.UNDEFINED_CLASS || errorCode == StaticWarningCode.UNDEFINED_CLASS_BOOLEAN || errorCode == StaticWarningCode.UNDEFINED_IDENTIFIER || errorCode == StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER || errorCode == StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION || errorCode == StaticTypeWarningCode.UNDEFINED_FUNCTION || errorCode == StaticTypeWarningCode.UNDEFINED_GETTER || errorCode == StaticTypeWarningCode.UNDEFINED_METHOD; } private void addFix_addPackageDependency() throws Exception { if (node instanceof SimpleStringLiteral && node.getParent() instanceof NamespaceDirective) { SimpleStringLiteral uriLiteral = (SimpleStringLiteral) node; String uriString = uriLiteral.getValue(); // we need package: import if (!uriString.startsWith("package:")) { return; } // prepare package name String packageName = StringUtils.removeStart(uriString, "package:"); packageName = StringUtils.substringBefore(packageName, "/"); // add proposal proposals.add(new AddDependencyCorrectionProposal( unitFile, packageName, CorrectionKind.QF_ADD_PACKAGE_DEPENDENCY, packageName)); } } private void addFix_boolInsteadOfBoolean() { SourceRange range = rangeError(problem); addReplaceEdit(range, "bool"); addUnitCorrectionProposal(CorrectionKind.QF_REPLACE_BOOLEAN_WITH_BOOL); } private void addFix_createClass() { if (mayBeTypeIdentifier(node)) { String name = ((SimpleIdentifier) node).getName(); // prepare environment String eol = utils.getEndOfLine(); CompilationUnitMember enclosingMember = node.getAncestor(CompilationUnitMember.class); int offset = enclosingMember.getEnd(); String prefix = ""; // prepare source SourceBuilder sb = new SourceBuilder(offset); { sb.append(eol + eol); sb.append(prefix); // "class" sb.append("class "); // append name { sb.startPosition("NAME"); sb.append(name); sb.endPosition(); } // no members sb.append(" {"); sb.append(eol); sb.append("}"); } // insert source addInsertEdit(offset, sb.toString()); // add linked positions addLinkedPosition("NAME", rangeNode(node)); addLinkedPositions(sb); // add proposal addUnitCorrectionProposal(CorrectionKind.QF_CREATE_CLASS, name); } } private void addFix_createConstructor_insteadOfSyntheticDefault() throws Exception { TypeName typeName = null; ConstructorName constructorName = null; InstanceCreationExpression instanceCreation = null; if (node instanceof SimpleIdentifier) { if (node.getParent() instanceof TypeName) { typeName = (TypeName) node.getParent(); if (typeName.getName() == node && typeName.getParent() instanceof ConstructorName) { constructorName = (ConstructorName) typeName.getParent(); // should be synthetic default constructor { ConstructorElement constructorElement = constructorName.getStaticElement(); if (constructorElement == null || !constructorElement.isDefaultConstructor() || !constructorElement.isSynthetic()) { return; } } // prepare InstanceCreationExpression if (constructorName.getParent() instanceof InstanceCreationExpression) { instanceCreation = (InstanceCreationExpression) constructorName.getParent(); if (instanceCreation.getConstructorName() != constructorName) { return; } } } } } // do we have enough information? if (instanceCreation == null) { return; } // prepare environment String eol = utils.getEndOfLine(); // prepare target Type targetType = typeName.getType(); if (!(targetType instanceof InterfaceType)) { return; } ClassElement targetElement = (ClassElement) targetType.getElement(); Source targetSource = targetElement.getSource(); ClassDeclaration targetClass = targetElement.getNode(); NewConstructorLocation targetLocation = prepareNewConstructorLocation(targetClass, eol); // build method source SourceBuilder sb = new SourceBuilder(targetLocation.offset); { String indent = " "; sb.append(targetLocation.prefix); sb.append(indent); sb.append(targetElement.getName()); addFix_undefinedMethod_create_parameters(sb, instanceCreation.getArgumentList()); sb.append(") {" + eol + indent + "}"); sb.append(targetLocation.suffix); } // insert source addInsertEdit(sb); // add linked positions addLinkedPositions(sb); // add proposal addUnitCorrectionProposal(targetSource, CorrectionKind.QF_CREATE_CONSTRUCTOR, constructorName); } private void addFix_createConstructor_named() throws Exception { SimpleIdentifier name = null; ConstructorName constructorName = null; InstanceCreationExpression instanceCreation = null; if (node instanceof SimpleIdentifier) { // name name = (SimpleIdentifier) node; if (name.getParent() instanceof ConstructorName) { constructorName = (ConstructorName) name.getParent(); if (constructorName.getName() == name) { // Type.name if (constructorName.getParent() instanceof InstanceCreationExpression) { instanceCreation = (InstanceCreationExpression) constructorName.getParent(); // new Type.name() if (instanceCreation.getConstructorName() != constructorName) { return; } } } } } // do we have enough information? if (instanceCreation == null) { return; } // prepare environment String eol = utils.getEndOfLine(); // prepare target interface type Type targetType = constructorName.getType().getType(); if (!(targetType instanceof InterfaceType)) { return; } ClassElement targetElement = (ClassElement) targetType.getElement(); Source targetSource = targetElement.getSource(); ClassDeclaration targetClass = targetElement.getNode(); NewConstructorLocation targetLocation = prepareNewConstructorLocation(targetClass, eol); // build method source SourceBuilder sb = new SourceBuilder(targetLocation.offset); { String indent = " "; sb.append(targetLocation.prefix); sb.append(indent); sb.append(targetElement.getName()); sb.append("."); // append name { sb.startPosition("NAME"); sb.append(name.getName()); sb.endPosition(); } addFix_undefinedMethod_create_parameters(sb, instanceCreation.getArgumentList()); sb.append(") {" + eol + indent + "}"); sb.append(targetLocation.suffix); } // insert source addInsertEdit(sb); // add linked positions if (Objects.equal(targetSource, source)) { addLinkedPosition("NAME", sb, rangeNode(name)); } addLinkedPositions(sb); // add proposal addUnitCorrectionProposal(targetSource, CorrectionKind.QF_CREATE_CONSTRUCTOR, constructorName); } /** * @see StaticWarningCode#NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT */ private void addFix_createConstructorSuperExplicit() { ConstructorDeclaration targetConstructor = (ConstructorDeclaration) node.getParent(); ClassDeclaration targetClassNode = (ClassDeclaration) targetConstructor.getParent(); ClassElement targetClassElement = targetClassNode.getElement(); ClassElement superClassElement = targetClassElement.getSupertype().getElement(); // add proposals for all super constructors ConstructorElement[] superConstructors = superClassElement.getConstructors(); for (ConstructorElement superConstructor : superConstructors) { String constructorName = superConstructor.getName(); // skip private if (Identifier.isPrivateName(constructorName)) { continue; } // prepare SourceBuilder SourceBuilder sb; { List<ConstructorInitializer> initializers = targetConstructor.getInitializers(); if (initializers.isEmpty()) { int insertOffset = targetConstructor.getParameters().getEnd(); sb = new SourceBuilder(insertOffset); sb.append(" : "); } else { ConstructorInitializer lastInitializer = initializers.get(initializers.size() - 1); int insertOffset = lastInitializer.getEnd(); sb = new SourceBuilder(insertOffset); sb.append(", "); } } // add super constructor name sb.append("super"); if (!StringUtils.isEmpty(constructorName)) { sb.append("."); sb.append(constructorName); } // add arguments sb.append("("); boolean firstParameter = true; for (ParameterElement parameter : superConstructor.getParameters()) { // skip non-required parameters if (parameter.getParameterKind() != ParameterKind.REQUIRED) { break; } // comma if (firstParameter) { firstParameter = false; } else { sb.append(", "); } // default value Type parameterType = parameter.getType(); sb.startPosition(parameter.getName()); sb.append(CorrectionUtils.getDefaultValueCode(parameterType)); sb.endPosition(); } sb.append(")"); // insert proposal addLinkedPositions(sb); addInsertEdit(sb); // add proposal String proposalName = getConstructorProposalName(superConstructor); addUnitCorrectionProposal(CorrectionKind.QF_ADD_SUPER_CONSTRUCTOR_INVOCATION, proposalName); } } /** * @see StaticWarningCode#NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT */ private void addFix_createConstructorSuperImplicit() { ClassDeclaration targetClassNode = (ClassDeclaration) node.getParent(); ClassElement targetClassElement = targetClassNode.getElement(); ClassElement superClassElement = targetClassElement.getSupertype().getElement(); String targetClassName = targetClassElement.getName(); // add proposals for all super constructors ConstructorElement[] superConstructors = superClassElement.getConstructors(); for (ConstructorElement superConstructor : superConstructors) { String constructorName = superConstructor.getName(); // skip private if (Identifier.isPrivateName(constructorName)) { continue; } // prepare parameters and arguments StringBuilder parametersBuffer = new StringBuilder(); StringBuilder argumentsBuffer = new StringBuilder(); boolean firstParameter = true; for (ParameterElement parameter : superConstructor.getParameters()) { // skip non-required parameters if (parameter.getParameterKind() != ParameterKind.REQUIRED) { break; } // comma if (firstParameter) { firstParameter = false; } else { parametersBuffer.append(", "); argumentsBuffer.append(", "); } // name String parameterName = parameter.getDisplayName(); if (parameterName.length() > 1 && parameterName.startsWith("_")) { parameterName = parameterName.substring(1); } // parameter & argument appendParameterSource(parametersBuffer, parameter.getType(), parameterName); argumentsBuffer.append(parameterName); } // add proposal String eol = utils.getEndOfLine(); NewConstructorLocation targetLocation = prepareNewConstructorLocation(targetClassNode, eol); SourceBuilder sb = new SourceBuilder(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); sb.append(") : super"); if (!constructorName.isEmpty()) { sb.append("."); sb.append(constructorName); } sb.append("("); sb.append(argumentsBuffer); sb.append(");"); sb.append(targetLocation.suffix); } addInsertEdit(sb); // add proposal String proposalName = getConstructorProposalName(superConstructor); addUnitCorrectionProposal(CorrectionKind.QF_CREATE_CONSTRUCTOR_SUPER, proposalName); } } private void addFix_createFunction_forFunctionType() throws Exception { if (node instanceof SimpleIdentifier) { SimpleIdentifier nameNode = (SimpleIdentifier) node; // prepare argument expression (to get parameter) ClassElement targetElement; Expression argument; { Expression target = CorrectionUtils.getQualifiedPropertyTarget(node); if (target != null) { Type targetType = target.getBestType(); if (targetType != null && targetType.getElement() instanceof ClassElement) { targetElement = (ClassElement) targetType.getElement(); argument = (Expression) target.getParent(); } else { return; } } else { ClassDeclaration enclosingClass = node.getAncestor(ClassDeclaration.class); targetElement = enclosingClass != null ? enclosingClass.getElement() : null; argument = nameNode; } } // should be argument of some invocation ParameterElement parameterElement = argument.getBestParameterElement(); if (parameterElement == null) { return; } // should be parameter of function type Type parameterType = parameterElement.getType(); if (!(parameterType instanceof FunctionType)) { return; } FunctionType functionType = (FunctionType) parameterType; // add proposal if (targetElement != null) { addProposal_createFunction_method(targetElement, functionType); } else { addProposal_createFunction_function(functionType); } } } private void addFix_createMissingOverrides(ExecutableElement[] missingOverrides) throws Exception { // sort by name Arrays.sort(missingOverrides, new Comparator<Element>() { @Override public int compare(Element firstElement, Element secondElement) { return ObjectUtils.compare(firstElement.getDisplayName(), secondElement.getDisplayName()); } }); // add elements ClassDeclaration targetClass = (ClassDeclaration) node.getParent(); boolean isFirst = true; for (ExecutableElement missingOverride : missingOverrides) { addFix_createMissingOverrides_single(targetClass, missingOverride, isFirst); isFirst = false; } // add proposal addUnitCorrectionProposal(CorrectionKind.QF_CREATE_MISSING_OVERRIDES, missingOverrides.length); } private void addFix_createMissingOverrides_single(ClassDeclaration targetClass, ExecutableElement missingOverride, boolean isFirst) throws Exception { // prepare environment String eol = utils.getEndOfLine(); String prefix = utils.getIndent(1); String prefix2 = utils.getIndent(2); int insertOffset = targetClass.getEnd() - 1; // prepare source StringBuilder sb = new StringBuilder(); // may be empty line if (!isFirst || !targetClass.getMembers().isEmpty()) { sb.append(eol); } // may be property ElementKind elementKind = missingOverride.getKind(); boolean isGetter = elementKind == ElementKind.GETTER; boolean isSetter = elementKind == ElementKind.SETTER; boolean isMethod = elementKind == ElementKind.METHOD; boolean isOperator = isMethod && ((MethodElement) missingOverride).isOperator(); sb.append(prefix); if (isGetter) { sb.append("// TODO: implement " + missingOverride.getDisplayName()); sb.append(eol); sb.append(prefix); } // @override { sb.append("@override"); sb.append(eol); sb.append(prefix); } // return type appendType(sb, missingOverride.getType().getReturnType()); if (isGetter) { sb.append("get "); } else if (isSetter) { sb.append("set "); } else if (isOperator) { sb.append("operator "); } // name sb.append(missingOverride.getDisplayName()); // parameters + body if (isGetter) { sb.append(" => null;"); } else if (isMethod || isSetter) { ParameterElement[] parameters = missingOverride.getParameters(); appendParameters(sb, parameters); sb.append(" {"); // TO-DO sb.append(eol); sb.append(prefix2); if (isMethod) { sb.append("// TODO: implement " + missingOverride.getDisplayName()); } else { sb.append("// TODO: implement " + missingOverride.getDisplayName()); } 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 = rangeStartLength(insertOffset, 0); } } private void addFix_createNoSuchMethod() throws Exception { ClassDeclaration targetClass = (ClassDeclaration) node.getParent(); // prepare environment String eol = utils.getEndOfLine(); String prefix = utils.getIndent(1); int insertOffset = targetClass.getEnd() - 1; // prepare source SourceBuilder sb = new SourceBuilder(insertOffset); { // insert empty line before existing member if (!targetClass.getMembers().isEmpty()) { sb.append(eol); } // append method sb.append(prefix); sb.append("noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);"); sb.append(eol); } // done addInsertEdit(sb); endRange = rangeStartLength(insertOffset, 0); // add proposal addUnitCorrectionProposal(CorrectionKind.QF_CREATE_NO_SUCH_METHOD); } private void addFix_createPart() throws Exception { if (node instanceof SimpleStringLiteral && node.getParent() instanceof PartDirective) { SimpleStringLiteral uriLiteral = (SimpleStringLiteral) node; String uriString = uriLiteral.getValue(); // prepare referenced File File newFile; { URI uri = URI.create(uriString); if (uri.isAbsolute()) { return; } newFile = new File(unitLibraryFolder, uriString); } if (!newFile.exists()) { // prepare new source String source; { String eol = utils.getEndOfLine(); String libraryName = unitLibraryElement.getDisplayName(); source = "part of " + libraryName + ";" + eol + eol; } // add proposal proposals.add(new CreateFileCorrectionProposal( newFile, source, CorrectionKind.QF_CREATE_PART, uriString)); } } } private void addFix_importLibrary(CorrectionKind kind, String importPath) throws Exception { CompilationUnitElement libraryUnitElement = unitLibraryElement.getDefiningCompilationUnit(); CompilationUnit libraryUnit = libraryUnitElement.getNode(); // prepare new import location int offset = 0; String prefix; String suffix; { String eol = utils.getEndOfLine(); // if no directives prefix = ""; suffix = eol; CorrectionUtils libraryUtils = new CorrectionUtils(libraryUnit); // after last directive in library for (Directive directive : libraryUnit.getDirectives()) { if (directive instanceof LibraryDirective || directive instanceof ImportDirective) { offset = directive.getEnd(); prefix = eol; suffix = ""; } } // if still beginning of file, skip shebang and line comments if (offset == 0) { InsertDesc desc = libraryUtils.getInsertDescTop(); offset = desc.offset; prefix = desc.prefix; suffix = desc.suffix + eol; } } // insert new import String importSource = prefix + "import '" + importPath + "';" + suffix; addInsertEdit(offset, importSource); // add proposal addUnitCorrectionProposal(libraryUnitElement.getSource(), kind, importPath); } private void addFix_importLibrary_withElement(String name, ElementKind kind) throws Exception { // ignore if private if (name.startsWith("_")) { return; } // may be there is an existing import, but it is with prefix and we don't use this prefix for (ImportElement imp : unitLibraryElement.getImports()) { // prepare element LibraryElement libraryElement = imp.getImportedLibrary(); Element element = CorrectionUtils.getExportedElement(libraryElement, name); if (element == null) { continue; } if (element instanceof PropertyAccessorElement) { element = ((PropertyAccessorElement) element).getVariable(); } if (element.getKind() != kind) { continue; } // may be apply prefix PrefixElement prefix = imp.getPrefix(); if (prefix != null) { SourceRange range = rangeStartLength(node, 0); addReplaceEdit(range, prefix.getDisplayName() + "."); addUnitCorrectionProposal( CorrectionKind.QF_IMPORT_LIBRARY_PREFIX, libraryElement.getDisplayName(), prefix.getDisplayName()); continue; } // may be update "show" directive NamespaceCombinator[] combinators = imp.getCombinators(); if (combinators.length == 1 && combinators[0] instanceof ShowElementCombinator) { ShowElementCombinator showCombinator = (ShowElementCombinator) combinators[0]; // prepare new set of names to show Set<String> showNames = Sets.newTreeSet(); Collections.addAll(showNames, showCombinator.getShownNames()); showNames.add(name); // prepare library name - unit name or 'dart:name' for SDK library String libraryName = libraryElement.getDefiningCompilationUnit().getDisplayName(); if (libraryElement.isInSdk()) { libraryName = imp.getUri(); } // update library String newShowCode = "show " + StringUtils.join(showNames, ", "); addReplaceEdit(rangeShowCombinator(showCombinator), newShowCode); addUnitCorrectionProposal( unitLibraryElement.getSource(), CorrectionKind.QF_IMPORT_LIBRARY_SHOW, libraryName); // we support only one import without prefix return; } } // check SDK libraries AnalysisContext context = unitLibraryElement.getContext(); { DartSdk sdk = context.getSourceFactory().getDartSdk(); SdkLibrary[] sdkLibraries = sdk.getSdkLibraries(); for (SdkLibrary sdkLibrary : sdkLibraries) { SourceFactory sdkSourceFactory = context.getSourceFactory(); String libraryUri = sdkLibrary.getShortName(); Source librarySource = sdkSourceFactory.resolveUri(null, libraryUri); // prepare LibraryElement LibraryElement libraryElement = context.getLibraryElement(librarySource); if (libraryElement == null) { continue; } // prepare exported Element Element element = CorrectionUtils.getExportedElement(libraryElement, name); if (element == null) { continue; } if (element instanceof PropertyAccessorElement) { element = ((PropertyAccessorElement) element).getVariable(); } if (element.getKind() != kind) { continue; } // add import addFix_importLibrary(CorrectionKind.QF_IMPORT_LIBRARY_SDK, libraryUri); } } // check project libraries { Source[] librarySources = context.getLibrarySources(); for (Source librarySource : librarySources) { // we don't need SDK libraries here if (librarySource.isInSystemLibrary()) { continue; } // prepare LibraryElement LibraryElement libraryElement = context.getLibraryElement(librarySource); if (libraryElement == null) { continue; } // prepare exported Element Element element = CorrectionUtils.getExportedElement(libraryElement, name); if (element == null) { continue; } if (element.getKind() != kind) { continue; } // prepare "library" file File libraryFile = getSourceFile(librarySource); if (libraryFile == null) { continue; } // may be "package:" URI { URI libraryPackageUri = findPackageUri(context, libraryFile); if (libraryPackageUri != null) { addFix_importLibrary( CorrectionKind.QF_IMPORT_LIBRARY_PROJECT, libraryPackageUri.toString()); continue; } } // relative URI String relative = URIUtils.computeRelativePath( unitLibraryFolder.getAbsolutePath(), libraryFile.getAbsolutePath()); addFix_importLibrary(CorrectionKind.QF_IMPORT_LIBRARY_PROJECT, relative); } } } private void addFix_importLibrary_withFunction() throws Exception { if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) node.getParent(); if (invocation.getRealTarget() == null && invocation.getMethodName() == node) { String name = ((SimpleIdentifier) node).getName(); addFix_importLibrary_withElement(name, ElementKind.FUNCTION); } } } private void addFix_importLibrary_withTopLevelVariable() throws Exception { if (node instanceof SimpleIdentifier) { String name = ((SimpleIdentifier) node).getName(); addFix_importLibrary_withElement(name, ElementKind.TOP_LEVEL_VARIABLE); } } private void addFix_importLibrary_withType() throws Exception { if (mayBeTypeIdentifier(node)) { String typeName = ((SimpleIdentifier) node).getName(); addFix_importLibrary_withElement(typeName, ElementKind.CLASS); } } private void addFix_insertSemicolon() { if (problem.getMessage().contains("';'")) { int insertOffset = problem.getOffset() + problem.getLength(); addInsertEdit(insertOffset, ";"); addUnitCorrectionProposal(CorrectionKind.QF_INSERT_SEMICOLON); } } private void addFix_isNotNull() throws Exception { if (coveredNode instanceof IsExpression) { IsExpression isExpression = (IsExpression) coveredNode; addReplaceEdit(rangeEndEnd(isExpression.getExpression(), isExpression), " != null"); addUnitCorrectionProposal(CorrectionKind.QF_USE_NOT_EQ_NULL); } } private void addFix_isNull() throws Exception { if (coveredNode instanceof IsExpression) { IsExpression isExpression = (IsExpression) coveredNode; addReplaceEdit(rangeEndEnd(isExpression.getExpression(), isExpression), " == null"); addUnitCorrectionProposal(CorrectionKind.QF_USE_EQ_EQ_NULL); } } private void addFix_makeEnclosingClassAbstract() { ClassDeclaration enclosingClass = node.getAncestor(ClassDeclaration.class); String className = enclosingClass.getName().getName(); addInsertEdit(enclosingClass.getClassKeyword().getOffset(), "abstract "); addUnitCorrectionProposal(CorrectionKind.QF_MAKE_CLASS_ABSTRACT, className); } private void addFix_removeParameters_inGetterDeclaration() throws Exception { if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodDeclaration) { MethodDeclaration method = (MethodDeclaration) node.getParent(); FunctionBody body = method.getBody(); if (method.getName() == node && body != null) { addReplaceEdit(rangeEndStart(node, body), " "); addUnitCorrectionProposal(CorrectionKind.QF_REMOVE_PARAMETERS_IN_GETTER_DECLARATION); } } } private void addFix_removeParentheses_inGetterInvocation() throws Exception { if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) node.getParent(); if (invocation.getMethodName() == node && invocation.getTarget() != null) { addRemoveEdit(rangeEndEnd(node, invocation)); addUnitCorrectionProposal(CorrectionKind.QF_REMOVE_PARENTHESIS_IN_GETTER_INVOCATION); } } } private void addFix_removeUnnecessaryCast() { if (!(coveredNode instanceof AsExpression)) { return; } AsExpression asExpression = (AsExpression) coveredNode; Expression expression = asExpression.getExpression(); int expressionPrecedence = CorrectionUtils.getExpressionPrecedence(expression); // remove 'as T' from 'e as T' addRemoveEdit(rangeEndEnd(expression, asExpression)); removeEnclosingParentheses(asExpression, expressionPrecedence); // done addUnitCorrectionProposal(CorrectionKind.QF_REMOVE_UNNECASSARY_CAST); } private void addFix_removeUnusedImport() { // prepare ImportDirective ImportDirective importDirective = node.getAncestor(ImportDirective.class); if (importDirective == null) { return; } // remove the whole line with import addRemoveEdit(utils.getLinesRange(rangeNode(importDirective))); // done addUnitCorrectionProposal(CorrectionKind.QF_REMOVE_UNUSED_IMPORT); } private void addFix_replaceWithConstInstanceCreation() throws Exception { if (coveredNode instanceof InstanceCreationExpression) { InstanceCreationExpression instanceCreation = (InstanceCreationExpression) coveredNode; addReplaceEdit(rangeToken(instanceCreation.getKeyword()), "const"); addUnitCorrectionProposal(CorrectionKind.QF_USE_CONST); } } private void addFix_undefinedClass_useSimilar() { if (mayBeTypeIdentifier(node)) { String name = ((SimpleIdentifier) node).getName(); final ClosestElementFinder finder = new ClosestElementFinder(name, new Predicate<Element>() { @Override public boolean apply(Element element) { return element instanceof ClassElement; } }); // find closest element { // elements of this library unitLibraryElement.accept(new RecursiveElementVisitor<Void>() { @Override public Void visitClassElement(ClassElement element) { finder.update(element); return null; } }); // elements from imports for (ImportElement importElement : unitLibraryElement.getImports()) { if (importElement.getPrefix() == null) { Map<String, Element> namespace = CorrectionUtils.getImportNamespace(importElement); finder.update(namespace.values()); } } } // if we have close enough element, suggest to use it if (finder != null && finder.distance < 5) { String closestName = finder.element.getName(); addReplaceEdit(rangeNode(node), closestName); // add proposal if (closestName != null) { addUnitCorrectionProposal(CorrectionKind.QF_CHANGE_TO, closestName); } } } } private void addFix_undefinedFunction_create() throws Exception { // should be the name of the invocation if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodInvocation) { } else { return; } String name = ((SimpleIdentifier) node).getName(); MethodInvocation invocation = (MethodInvocation) node.getParent(); // function invocation has no target Expression target = invocation.getRealTarget(); if (target != null) { return; } // prepare environment String eol = utils.getEndOfLine(); int insertOffset; String sourcePrefix; AstNode enclosingMember = node.getAncestor(CompilationUnitMember.class); insertOffset = enclosingMember.getEnd(); sourcePrefix = eol + eol; // build method source SourceBuilder sb = new SourceBuilder(insertOffset); { sb.append(sourcePrefix); // may be return type { Type type = addFix_undefinedMethod_create_getReturnType(invocation); if (type != null) { String typeSource = utils.getTypeSource(type); if (!typeSource.equals("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.getArgumentList()); sb.append(") {" + eol + "}"); } // insert source addInsertEdit(insertOffset, sb.toString()); // add linked positions addLinkedPosition("NAME", sb, rangeNode(node)); addLinkedPositions(sb); // add proposal addUnitCorrectionProposal(CorrectionKind.QF_CREATE_FUNCTION, name); } private void addFix_undefinedFunction_useSimilar() throws Exception { if (node instanceof SimpleIdentifier) { String name = ((SimpleIdentifier) node).getName(); final ClosestElementFinder finder = new ClosestElementFinder(name, new Predicate<Element>() { @Override public boolean apply(Element element) { return element instanceof FunctionElement; } }); // this library unitLibraryElement.accept(new RecursiveElementVisitor<Void>() { @Override public Void visitFunctionElement(FunctionElement element) { finder.update(element); return null; } }); // imports for (ImportElement importElement : unitLibraryElement.getImports()) { if (importElement.getPrefix() == null) { Map<String, Element> namespace = CorrectionUtils.getImportNamespace(importElement); finder.update(namespace.values()); } } // if we have close enough element, suggest to use it String closestName = null; if (finder != null && finder.distance < 5) { closestName = finder.element.getName(); addReplaceEdit(rangeNode(node), closestName); addUnitCorrectionProposal(CorrectionKind.QF_CHANGE_TO, closestName); } } } private void addFix_undefinedMethod_create() throws Exception { if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodInvocation) { String name = ((SimpleIdentifier) node).getName(); MethodInvocation invocation = (MethodInvocation) node.getParent(); // prepare environment String eol = utils.getEndOfLine(); Source targetSource; String prefix; int insertOffset; String sourcePrefix; String sourceSuffix; boolean staticModifier = false; Expression target = invocation.getRealTarget(); if (target == null) { targetSource = source; ClassMember enclosingMember = node.getAncestor(ClassMember.class); staticModifier = inStaticMemberContext(enclosingMember); prefix = utils.getNodePrefix(enclosingMember); insertOffset = enclosingMember.getEnd(); sourcePrefix = eol + prefix + eol; sourceSuffix = ""; } else { // prepare target interface type Type targetType = target.getBestType(); if (!(targetType instanceof InterfaceType)) { return; } ClassElement targetElement = (ClassElement) targetType.getElement(); targetSource = targetElement.getSource(); // may be static if (target instanceof Identifier) { staticModifier = ((Identifier) target).getBestElement().getKind() == ElementKind.CLASS; } // prepare insert offset ClassDeclaration targetClass = targetElement.getNode(); prefix = " "; insertOffset = targetClass.getEnd() - 1; if (targetClass.getMembers().isEmpty()) { sourcePrefix = ""; } else { sourcePrefix = prefix + eol; } sourceSuffix = eol; } // build method source SourceBuilder sb = new SourceBuilder(insertOffset); { sb.append(sourcePrefix); sb.append(prefix); // may be "static" if (staticModifier) { sb.append("static "); } // may be return type { Type type = addFix_undefinedMethod_create_getReturnType(invocation); if (type != null) { String typeSource = utils.getTypeSource(type); if (!typeSource.equals("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.getArgumentList()); sb.append(") {" + eol + prefix + "}"); sb.append(sourceSuffix); } // insert source addInsertEdit(insertOffset, sb.toString()); // add linked positions if (Objects.equal(targetSource, source)) { addLinkedPosition("NAME", sb, rangeNode(node)); } addLinkedPositions(sb); // add proposal addUnitCorrectionProposal(targetSource, CorrectionKind.QF_CREATE_METHOD, name); } } /** * @return the possible return {@link Type}, may be <code>null</code> if can not be identified. */ private Type addFix_undefinedMethod_create_getReturnType(MethodInvocation invocation) { AstNode parent = invocation.getParent(); // myFunction(); if (parent instanceof ExpressionStatement) { return VoidTypeImpl.getInstance(); } // return myFunction(); if (parent instanceof ReturnStatement) { ExecutableElement executable = CorrectionUtils.getEnclosingExecutableElement(invocation); return executable != null ? executable.getReturnType() : null; } // int v = myFunction(); if (parent instanceof VariableDeclaration) { VariableDeclaration variableDeclaration = (VariableDeclaration) parent; if (variableDeclaration.getInitializer() == invocation) { VariableElement variableElement = variableDeclaration.getElement(); if (variableElement != null) { return variableElement.getType(); } } } // v = myFunction(); if (parent instanceof AssignmentExpression) { AssignmentExpression assignment = (AssignmentExpression) parent; if (assignment.getRightHandSide() == invocation) { if (assignment.getOperator().getType() == TokenType.EQ) { // v = myFunction(); Expression lhs = assignment.getLeftHandSide(); if (lhs != null) { return lhs.getBestType(); } } else { // v += myFunction(); MethodElement method = assignment.getBestElement(); if (method != null) { ParameterElement[] parameters = method.getParameters(); if (parameters.length == 1) { return parameters[0].getType(); } } } } } // v + myFunction(); if (parent instanceof BinaryExpression) { BinaryExpression binary = (BinaryExpression) parent; MethodElement method = binary.getBestElement(); if (method != null) { if (binary.getRightOperand() == invocation) { ParameterElement[] parameters = method.getParameters(); return parameters.length == 1 ? parameters[0].getType() : null; } } } // foo( myFunction() ); if (parent instanceof ArgumentList) { ParameterElement parameter = invocation.getBestParameterElement(); return parameter != null ? parameter.getType() : null; } // bool { // assert( myFunction() ); if (parent instanceof AssertStatement) { AssertStatement statement = (AssertStatement) parent; if (statement.getCondition() == invocation) { return getCoreTypeBool(); } } // if ( myFunction() ) {} if (parent instanceof IfStatement) { IfStatement statement = (IfStatement) parent; if (statement.getCondition() == invocation) { return getCoreTypeBool(); } } // while ( myFunction() ) {} if (parent instanceof WhileStatement) { WhileStatement statement = (WhileStatement) parent; if (statement.getCondition() == invocation) { return getCoreTypeBool(); } } // do {} while ( myFunction() ); if (parent instanceof DoStatement) { DoStatement statement = (DoStatement) parent; if (statement.getCondition() == invocation) { return getCoreTypeBool(); } } // !myFunction() if (parent instanceof PrefixExpression) { PrefixExpression prefixExpression = (PrefixExpression) parent; if (prefixExpression.getOperator().getType() == TokenType.BANG) { return getCoreTypeBool(); } } // binary expression '&&' or '||' if (parent instanceof BinaryExpression) { BinaryExpression binaryExpression = (BinaryExpression) parent; TokenType operatorType = binaryExpression.getOperator().getType(); if (operatorType == TokenType.AMPERSAND_AMPERSAND || operatorType == TokenType.BAR_BAR) { return getCoreTypeBool(); } } } // we don't know return null; } private void addFix_undefinedMethod_create_parameters(SourceBuilder sb, ArgumentList argumentList) { // append parameters sb.append("("); Set<String> excluded = Sets.newHashSet(); List<Expression> arguments = argumentList.getArguments(); for (int i = 0; i < arguments.size(); i++) { Expression argument = arguments.get(i); // append separator if (i != 0) { sb.append(", "); } // append type name Type type = argument.getBestType(); String typeSource = utils.getTypeSource(type); { sb.startPosition("TYPE" + i); sb.append(typeSource); addSuperTypeProposals(sb, Sets.<Type> newHashSet(), type); sb.endPosition(); } sb.append(" "); // append parameter name { String[] suggestions = getArgumentNameSuggestions(excluded, type, argument, i); String favorite = suggestions[0]; excluded.add(favorite); sb.startPosition("ARG" + i); sb.append(favorite); sb.setProposals(suggestions); sb.endPosition(); } } } private void addFix_undefinedMethod_useSimilar() throws Exception { if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) node.getParent(); String name = ((SimpleIdentifier) node).getName(); ClosestElementFinder finder = new ClosestElementFinder(name, new Predicate<Element>() { @Override public boolean apply(Element element) { if (element instanceof MethodElement) { MethodElement methodElement = (MethodElement) element; return !methodElement.isOperator(); } return false; } }); // unqualified invocation Expression target = invocation.getRealTarget(); if (target == null) { ClassDeclaration clazz = invocation.getAncestor(ClassDeclaration.class); if (clazz != null) { ClassElement classElement = clazz.getElement(); updateFinderWithClassMembers(finder, classElement); } } else { Type type = target.getBestType(); if (type instanceof InterfaceType) { ClassElement classElement = ((InterfaceType) type).getElement(); updateFinderWithClassMembers(finder, classElement); } } // if we have close enough element, suggest to use it String closestName = null; if (finder != null && finder.distance < 5) { closestName = finder.element.getName(); addReplaceEdit(rangeNode(node), closestName); addUnitCorrectionProposal(CorrectionKind.QF_CHANGE_TO, closestName); } } } private void addFix_useEffectiveIntegerDivision() throws Exception { for (AstNode n = node; n != null; n = n.getParent()) { if (n instanceof MethodInvocation && n.getOffset() == selectionOffset && n.getLength() == selectionLength) { MethodInvocation invocation = (MethodInvocation) n; Expression target = invocation.getTarget(); while (target instanceof ParenthesizedExpression) { target = ((ParenthesizedExpression) target).getExpression(); } // replace "/" with "~/" BinaryExpression binary = (BinaryExpression) target; addReplaceEdit(rangeToken(binary.getOperator()), "~/"); // remove everything before and after addRemoveEdit(rangeStartStart(invocation, binary.getLeftOperand())); addRemoveEdit(rangeEndEnd(binary.getRightOperand(), invocation)); // add proposal addUnitCorrectionProposal(CorrectionKind.QF_USE_EFFECTIVE_INTEGER_DIVISION); // done break; } } } private void addFix_useStaticAccess_method() throws Exception { if (node instanceof SimpleIdentifier && node.getParent() instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) node.getParent(); if (invocation.getMethodName() == node) { Expression target = invocation.getTarget(); String targetType = utils.getTypeSource(target); // replace "target" with class name SourceRange range = SourceRangeFactory.rangeNode(target); addReplaceEdit(range, targetType); // add proposal addUnitCorrectionProposal(CorrectionKind.QF_CHANGE_TO_STATIC_ACCESS, targetType); } } } private void addFix_useStaticAccess_property() throws Exception { if (node instanceof SimpleIdentifier) { if (node.getParent() instanceof PrefixedIdentifier) { PrefixedIdentifier prefixed = (PrefixedIdentifier) node.getParent(); if (prefixed.getIdentifier() == node) { Expression target = prefixed.getPrefix(); String targetType = utils.getTypeSource(target); // replace "target" with class name SourceRange range = SourceRangeFactory.rangeNode(target); addReplaceEdit(range, targetType); // add proposal addUnitCorrectionProposal(CorrectionKind.QF_CHANGE_TO_STATIC_ACCESS, targetType); } } } } private void addInsertEdit(int offset, String text) { textEdits.add(createInsertEdit(offset, text)); } private void addInsertEdit(SourceBuilder builder) { addInsertEdit(builder.getOffset(), builder.toString()); } /** * Adds single linked position to the group. If {@link SourceBuilder} will be inserted before * "position", translate it. */ private void addLinkedPosition(String group, SourceBuilder sb, SourceRange position) { if (sb.getOffset() < position.getOffset()) { int delta = sb.length(); position = position.getTranslated(delta); } addLinkedPosition(group, position); } /** * Adds single linked position to the group. */ private void addLinkedPosition(String group, SourceRange position) { List<SourceRange> positions = linkedPositions.get(group); if (positions == null) { positions = Lists.newArrayList(); linkedPositions.put(group, positions); } positions.add(position); } private void addLinkedPositionProposal(String group, LinkedPositionProposal proposal) { List<LinkedPositionProposal> nodeProposals = linkedPositionProposals.get(group); if (nodeProposals == null) { nodeProposals = Lists.newArrayList(); linkedPositionProposals.put(group, nodeProposals); } nodeProposals.add(proposal); } /** * Adds positions from the given {@link SourceBuilder} to the {@link #linkedPositions}. */ private void addLinkedPositions(SourceBuilder builder) { // positions for (Entry<String, List<SourceRange>> linkedEntry : builder.getLinkedPositions().entrySet()) { String group = linkedEntry.getKey(); for (SourceRange position : linkedEntry.getValue()) { addLinkedPosition(group, position); } } // proposals for positions for (Entry<String, List<LinkedPositionProposal>> entry : builder.getLinkedProposals().entrySet()) { String group = entry.getKey(); for (LinkedPositionProposal proposal : entry.getValue()) { addLinkedPositionProposal(group, proposal); } } } /** * Prepares proposal for creating function corresponding to the given {@link FunctionType}. */ private void addProposal_createFunction(FunctionType functionType, String name, Source targetSource, int insertOffset, boolean isStatic, String eol, String prefix, String sourcePrefix, String sourceSuffix) { // build method source SourceBuilder sb = new SourceBuilder(insertOffset); { sb.append(sourcePrefix); sb.append(prefix); // may be static if (isStatic) { sb.append("static "); } // may be return type { Type returnType = functionType.getReturnType(); if (returnType != null) { String typeSource = utils.getTypeSource(returnType); if (!typeSource.equals("dynamic")) { sb.startPosition("RETURN_TYPE"); sb.append(typeSource); sb.endPosition(); sb.append(" "); } } } // append name { sb.startPosition("NAME"); sb.append(name); sb.endPosition(); } // append parameters sb.append("("); ParameterElement[] parameters = functionType.getParameters(); for (int i = 0; i < parameters.length; i++) { ParameterElement parameter = parameters[i]; // append separator if (i != 0) { sb.append(", "); } // append type name Type type = parameter.getType(); String typeSource = utils.getTypeSource(type); { sb.startPosition("TYPE" + i); sb.append(typeSource); addSuperTypeProposals(sb, Sets.<Type> newHashSet(), type); sb.endPosition(); } sb.append(" "); // append parameter name { sb.startPosition("ARG" + i); sb.append(parameter.getDisplayName()); sb.endPosition(); } } sb.append(")"); // close method sb.append(" {" + eol + prefix + "}"); sb.append(sourceSuffix); } // insert source addInsertEdit(insertOffset, sb.toString()); // add linked positions if (Objects.equal(targetSource, source)) { addLinkedPosition("NAME", sb, rangeNode(node)); } addLinkedPositions(sb); } /** * Adds proposal for creating method corresponding to the given {@link FunctionType} in the given * {@link ClassElement}. */ private void addProposal_createFunction_function(FunctionType functionType) throws Exception { String name = ((SimpleIdentifier) node).getName(); // prepare environment String eol = utils.getEndOfLine(); int insertOffset = unit.getEnd(); // prepare prefix String prefix = ""; String sourcePrefix = eol + eol; String sourceSuffix = eol; addProposal_createFunction( functionType, name, source, insertOffset, false, eol, prefix, sourcePrefix, sourceSuffix); // add proposal addUnitCorrectionProposal(source, CorrectionKind.QF_CREATE_FUNCTION, name); } /** * Adds proposal for creating method corresponding to the given {@link FunctionType} in the given * {@link ClassElement}. */ private void addProposal_createFunction_method(ClassElement targetClassElement, FunctionType functionType) throws Exception { String name = ((SimpleIdentifier) node).getName(); // prepare environment String eol = utils.getEndOfLine(); Source targetSource = targetClassElement.getSource(); // prepare insert offset ClassDeclaration targetClassNode = targetClassElement.getNode(); int insertOffset = targetClassNode.getEnd() - 1; // prepare prefix String prefix = " "; String sourcePrefix; if (targetClassNode.getMembers().isEmpty()) { sourcePrefix = ""; } else { sourcePrefix = prefix + eol; } String sourceSuffix = eol; addProposal_createFunction( functionType, name, targetSource, insertOffset, inStaticMemberContext(), eol, prefix, sourcePrefix, sourceSuffix); // add proposal addUnitCorrectionProposal(targetSource, CorrectionKind.QF_CREATE_METHOD, name); } private void addRemoveEdit(SourceRange range) { textEdits.add(createRemoveEdit(range)); } /** * Adds {@link Edit} to {@link #textEdits}. */ private void addReplaceEdit(SourceRange range, String text) { textEdits.add(createReplaceEdit(range, text)); } /** * Adds {@link CorrectionProposal} with single {@link SourceChange} to {@link #proposals}. */ private void addUnitCorrectionProposal(CorrectionKind kind, Object... arguments) { addUnitCorrectionProposal(source, kind, arguments); } /** * Adds {@link CorrectionProposal} with single {@link SourceChange} to {@link #proposals}. */ private void addUnitCorrectionProposal(Source source, CorrectionKind kind, Object... arguments) { if (!textEdits.isEmpty()) { // prepare SourceChange SourceChange change = new SourceChange(source.getShortName(), source); for (Edit edit : textEdits) { change.addEdit(edit); } // create SourceCorrectionProposal SourceCorrectionProposal proposal = new SourceCorrectionProposal(change, kind, arguments); proposal.setLinkedPositions(linkedPositions); proposal.setLinkedPositionProposals(linkedPositionProposals); proposal.setEndRange(endRange); // done proposals.add(proposal); } // reset resetProposalElements(); } private void appendParameters(StringBuilder sb, ParameterElement[] parameters) throws Exception { Map<ParameterElement, String> defaultValueMap = getDefaultValueMap(parameters); appendParameters(sb, parameters, defaultValueMap); } private void appendParameters(StringBuilder sb, ParameterElement[] parameters, Map<ParameterElement, String> defaultValueMap) { sb.append("("); boolean firstParameter = true; boolean sawNamed = false; boolean sawPositional = false; for (ParameterElement parameter : parameters) { if (!firstParameter) { sb.append(", "); } else { firstParameter = false; } // may be optional ParameterKind parameterKind = parameter.getParameterKind(); if (parameterKind == ParameterKind.NAMED) { if (!sawNamed) { sb.append("{"); sawNamed = true; } } if (parameterKind == ParameterKind.POSITIONAL) { if (!sawPositional) { sb.append("["); sawPositional = true; } } // parameter appendParameterSource(sb, parameter.getType(), parameter.getName()); // default value if (defaultValueMap != null) { String defaultSource = defaultValueMap.get(parameter); if (defaultSource != null) { if (sawPositional) { sb.append(" = "); } else { sb.append(": "); } sb.append(defaultSource); } } } // close parameters if (sawNamed) { sb.append("}"); } if (sawPositional) { sb.append("]"); } sb.append(")"); } private void appendParameterSource(StringBuilder sb, Type type, String name) { String parameterSource = utils.getParameterSource(type, name); sb.append(parameterSource); } private void appendType(StringBuilder sb, Type type) { if (type != null && !type.isDynamic()) { String typeSource = utils.getTypeSource(type); sb.append(typeSource); sb.append(" "); } } private Edit createInsertEdit(int offset, String text) { return new Edit(offset, 0, text); } /** * @return the string to display as the name of the given constructor in a proposal name. */ private String getConstructorProposalName(ConstructorElement constructor) { StringBuilder proposalNameBuffer = new StringBuilder(); proposalNameBuffer.append("super"); // may be named String constructorName = constructor.getDisplayName(); if (!constructorName.isEmpty()) { proposalNameBuffer.append("."); proposalNameBuffer.append(constructorName); } // parameters appendParameters(proposalNameBuffer, constructor.getParameters(), null); // done return proposalNameBuffer.toString(); } /** * Returns the {@link Type} with given name from the {@code dart:core} library. */ private Type getCoreType(String name) { LibraryElement[] libraries = unitLibraryElement.getImportedLibraries(); for (LibraryElement library : libraries) { if (library.isDartCore()) { ClassElement classElement = library.getType(name); if (classElement != null) { return classElement.getType(); } return null; } } return null; } private Type getCoreTypeBool() { return getCoreType("bool"); } private Map<ParameterElement, String> getDefaultValueMap(ParameterElement[] parameters) throws Exception { Map<ParameterElement, String> defaultSourceMap = Maps.newHashMap(); for (ParameterElement parameter : parameters) { defaultSourceMap.put(parameter, parameter.getDefaultValueCode()); } return defaultSourceMap; } /** * @return {@code true} if {@link #node} if part of a static method or any field initializer. */ private boolean inStaticMemberContext() { ClassMember member = node.getAncestor(ClassMember.class); return inStaticMemberContext(member); } /** * @return {@code true} if the given {@link ClassMember} is a part of a static method or any field * initializer. */ private boolean inStaticMemberContext(ClassMember member) { if (member instanceof MethodDeclaration) { return ((MethodDeclaration) member).isStatic(); } // field initializer cannot reference "this" if (member instanceof FieldDeclaration) { return true; } return false; } private NewConstructorLocation prepareNewConstructorLocation(ClassDeclaration classDeclaration, String eol) { List<ClassMember> members = classDeclaration.getMembers(); // find the last field/constructor ClassMember lastFieldOrConstructor = null; for (ClassMember member : members) { if (member instanceof FieldDeclaration || member instanceof ConstructorDeclaration) { lastFieldOrConstructor = member; } else { break; } } // after the field/constructor if (lastFieldOrConstructor != null) { return new NewConstructorLocation(eol + eol, lastFieldOrConstructor.getEnd(), ""); } // at the beginning of the class String suffix = members.isEmpty() ? "" : eol; return new NewConstructorLocation(eol, classDeclaration.getLeftBracket().getEnd(), suffix); } /** * Removes any {@link ParenthesizedExpression} enclosing the given {@link Expression}. * * @param expr the expression in {@link ParenthesizedExpression} * @param exprPrecedence the effective precedence of the "expr", may be not its * {@link Expression#getPrecedence()} */ private void removeEnclosingParentheses(Expression expr, int exprPrecedence) { while (expr.getParent() instanceof ParenthesizedExpression) { ParenthesizedExpression parenthesized = (ParenthesizedExpression) expr.getParent(); if (CorrectionUtils.getExpressionParentPrecedence(parenthesized) > exprPrecedence) { break; } addRemoveEdit(rangeToken(parenthesized.getLeftParenthesis())); addRemoveEdit(rangeToken(parenthesized.getRightParenthesis())); expr = parenthesized; } } private void resetProposalElements() { textEdits.clear(); linkedPositions.clear(); positionStopEdits.clear(); linkedPositionProposals.clear(); endRange = null; } private void updateFinderWithClassMembers(ClosestElementFinder finder, ClassElement classElement) { if (classElement != null) { List<Element> members = HierarchyUtils.getMembers(classElement, false); finder.update(members); } } }