/* * 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.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; 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.Block; import com.google.dart.engine.ast.BlockFunctionBody; import com.google.dart.engine.ast.ClassDeclaration; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.ConditionalExpression; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.ExpressionFunctionBody; 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.FunctionDeclaration; import com.google.dart.engine.ast.FunctionExpression; import com.google.dart.engine.ast.IfStatement; import com.google.dart.engine.ast.ImportDirective; import com.google.dart.engine.ast.IsExpression; import com.google.dart.engine.ast.LibraryIdentifier; import com.google.dart.engine.ast.MethodDeclaration; import com.google.dart.engine.ast.ParenthesizedExpression; import com.google.dart.engine.ast.PartOfDirective; import com.google.dart.engine.ast.PrefixExpression; import com.google.dart.engine.ast.PrefixedIdentifier; import com.google.dart.engine.ast.PropertyAccess; import com.google.dart.engine.ast.ReturnStatement; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.Statement; import com.google.dart.engine.ast.ThrowExpression; import com.google.dart.engine.ast.TopLevelVariableDeclaration; import com.google.dart.engine.ast.TypeName; import com.google.dart.engine.ast.VariableDeclaration; import com.google.dart.engine.ast.VariableDeclarationList; import com.google.dart.engine.ast.VariableDeclarationStatement; import com.google.dart.engine.ast.visitor.NodeLocator; import com.google.dart.engine.context.AnalysisContext; import com.google.dart.engine.element.CompilationUnitElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ImportElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.scanner.Keyword; import com.google.dart.engine.scanner.KeywordToken; import com.google.dart.engine.scanner.Token; import com.google.dart.engine.scanner.TokenClass; import com.google.dart.engine.scanner.TokenType; import com.google.dart.engine.search.SearchEngine; import com.google.dart.engine.search.SearchMatch; import com.google.dart.engine.services.assist.AssistContext; import com.google.dart.engine.services.change.Change; import com.google.dart.engine.services.change.CompositeChange; import com.google.dart.engine.services.change.CreateFileChange; import com.google.dart.engine.services.change.Edit; import com.google.dart.engine.services.change.SourceChange; import com.google.dart.engine.services.correction.ChangeCorrectionProposal; 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.LinkedPositionProposal; import com.google.dart.engine.services.correction.QuickAssistProcessor; import com.google.dart.engine.services.correction.SourceCorrectionProposal; import com.google.dart.engine.services.internal.correction.CorrectionUtils.InsertDesc; import com.google.dart.engine.services.internal.util.TokenUtils; import com.google.dart.engine.source.Source; 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.ast.ScopedNameFinder; 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 static com.google.dart.engine.services.correction.CorrectionKind.QA_ADD_TYPE_ANNOTATION; import static com.google.dart.engine.services.correction.CorrectionKind.QA_ASSIGN_TO_LOCAL_VARIABLE; import static com.google.dart.engine.services.correction.CorrectionKind.QA_CONVERT_INTO_BLOCK_BODY; import static com.google.dart.engine.services.correction.CorrectionKind.QA_CONVERT_INTO_EXPRESSION_BODY; import static com.google.dart.engine.services.correction.CorrectionKind.QA_CONVERT_INTO_IS_NOT; import static com.google.dart.engine.services.correction.CorrectionKind.QA_CONVERT_INTO_IS_NOT_EMPTY; import static com.google.dart.engine.services.correction.CorrectionKind.QA_EXCHANGE_OPERANDS; import static com.google.dart.engine.services.correction.CorrectionKind.QA_EXTRACT_CLASS; import static com.google.dart.engine.services.correction.CorrectionKind.QA_IMPORT_ADD_SHOW; import static com.google.dart.engine.services.correction.CorrectionKind.QA_INVERT_IF_STATEMENT; import static com.google.dart.engine.services.correction.CorrectionKind.QA_JOIN_IF_WITH_INNER; import static com.google.dart.engine.services.correction.CorrectionKind.QA_JOIN_IF_WITH_OUTER; import static com.google.dart.engine.services.correction.CorrectionKind.QA_JOIN_VARIABLE_DECLARATION; import static com.google.dart.engine.services.correction.CorrectionKind.QA_REMOVE_TYPE_ANNOTATION; import static com.google.dart.engine.services.correction.CorrectionKind.QA_REPLACE_CONDITIONAL_WITH_IF_ELSE; import static com.google.dart.engine.services.correction.CorrectionKind.QA_REPLACE_IF_ELSE_WITH_CONDITIONAL; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SPLIT_AND_CONDITION; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SPLIT_VARIABLE_DECLARATION; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_BLOCK; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_DO_WHILE; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_FOR; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_FOR_IN; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_IF; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_TRY_CATCH; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_TRY_FINALLY; import static com.google.dart.engine.services.correction.CorrectionKind.QA_SURROUND_WITH_WHILE; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndEnd; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndLength; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndStart; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeNode; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartEnd; 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 static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeWithBase; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.lang.reflect.Method; import java.net.URI; import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Implementation of {@link QuickAssistProcessor}. */ public class QuickAssistProcessorImpl implements QuickAssistProcessor { private static final CorrectionProposal[] NO_PROPOSALS = {}; /** * @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); } /** * @return <code>true</code> if selection covers operator of the given {@link BinaryExpression}. */ private static int isOperatorSelected(BinaryExpression binaryExpression, int offset, int length) { AstNode left = binaryExpression.getLeftOperand(); AstNode right = binaryExpression.getRightOperand(); if (isSelectingOperator(left, right, offset, length)) { return left.getEndToken().getEnd(); } return -1; } private static boolean isSelectingOperator(AstNode n1, AstNode n2, int offset, int length) { // between the nodes if (offset >= n1.getEndToken().getEnd() && offset + length <= n2.getOffset()) { return true; } // or exactly select the node (but not with infix expressions) if (offset == n1.getOffset() && offset + length == n2.getEndToken().getEnd()) { if (n1 instanceof BinaryExpression || n2 instanceof BinaryExpression) { return false; } return true; } // invalid selection (part of node, etc) return false; } /** * Checks if given {@link Expression} should be wrapped with parenthesis when we want to use it as * operand of logical "and" expression. */ private static boolean shouldWrapParenthesisBeforeAnd(Expression expr) { if (expr instanceof BinaryExpression) { BinaryExpression binary = (BinaryExpression) expr; int precedence = binary.getOperator().getType().getPrecedence(); return precedence < TokenClass.LOGICAL_AND_OPERATOR.getPrecedence(); } return false; } private final List<CorrectionProposal> proposals = Lists.newArrayList(); private final List<Edit> textEdits = Lists.newArrayList(); private AssistContext assistContext; private AnalysisContext analysisContext; private Source source; private CompilationUnit unit; private AstNode node; private Source unitLibrarySource; private LibraryElement unitLibraryElement; private File unitLibraryFile; private File unitLibraryFolder; 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 proposalEndRange = null; @Override public CorrectionProposal[] getProposals(AnalysisContext analysisContext, Source unitSource, CompilationUnit parsedUnit, int offset) throws Exception { this.analysisContext = analysisContext; this.source = unitSource; proposals.clear(); // prepare node node = new NodeLocator(offset).searchWithin(parsedUnit); if (node == null) { return NO_PROPOSALS; } // call proposal methods addUnresolvedProposal_addPart(); // done try { return proposals.toArray(new CorrectionProposal[proposals.size()]); } finally { cleanUp(); } } @Override public CorrectionProposal[] getProposals(AssistContext context) throws Exception { if (context == null) { return NO_PROPOSALS; } assistContext = context; proposals.clear(); source = context.getSource(); unit = context.getCompilationUnit(); node = context.getCoveringNode(); selectionOffset = context.getSelectionOffset(); selectionLength = context.getSelectionLength(); utils = new CorrectionUtils(context.getCompilationUnit()); // prepare elements { CompilationUnitElement unitElement = unit.getElement(); if (unitElement == null) { return NO_PROPOSALS; } unitLibraryElement = unitElement.getLibrary(); if (unitLibraryElement == null) { return NO_PROPOSALS; } unitLibrarySource = unitLibraryElement.getSource(); unitLibraryFile = QuickFixProcessorImpl.getSourceFile(unitLibrarySource); if (unitLibraryFile == null) { return NO_PROPOSALS; } unitLibraryFolder = unitLibraryFile.getParentFile(); } this.analysisContext = unitLibraryElement.getContext(); // run with instrumentation final InstrumentationBuilder instrumentation = Instrumentation.builder(this.getClass()); try { for (Method method : QuickAssistProcessorImpl.class.getDeclaredMethods()) { if (method.getName().startsWith("addProposal_")) { resetProposalElements(); try { method.invoke(QuickAssistProcessorImpl.this); } catch (Throwable e) { instrumentation.record(e); } } } instrumentation.metric("QuickAssist-Offset", selectionOffset); instrumentation.metric("QuickAssist-Length", selectionLength); instrumentation.metric("QuickAssist-ProposalCount", proposals.size()); instrumentation.data("QuickAssist-Source", utils.getText()); for (int index = 0; index < proposals.size(); index++) { instrumentation.data("QuickAssist-Proposal-" + index, proposals.get(index).getName()); } return proposals.toArray(new CorrectionProposal[proposals.size()]); } finally { instrumentation.log(); cleanUp(); } } void addProposal_addTypeAnnotation() throws Exception { // prepare VariableDeclarationList VariableDeclarationList declarationList = node.getAncestor(VariableDeclarationList.class); if (declarationList == null) { return; } // may be has type annotation already if (declarationList.getType() != null) { return; } // prepare single VariableDeclaration List<VariableDeclaration> variables = declarationList.getVariables(); if (variables.size() != 1) { return; } VariableDeclaration variable = variables.get(0); // we need variable to get type from Expression value = variable.getInitializer(); if (value == null) { return; } Type type = value.getBestType(); // prepare Type source String typeSource; if (type instanceof InterfaceType) { typeSource = utils.getTypeSource(type); } else if (type instanceof FunctionType) { typeSource = "Function"; } else { return; } // add edit { // find "var" token KeywordToken varToken; { SourceRange modifiersRange = rangeStartEnd(declarationList, variable); String modifiersSource = utils.getText(modifiersRange); List<Token> tokens = TokenUtils.getTokens(modifiersSource); varToken = TokenUtils.findKeywordToken(tokens, Keyword.VAR); } // replace "var", or insert type before name if (varToken != null) { SourceRange range = rangeToken(varToken); range = rangeWithBase(declarationList, range); addReplaceEdit(range, typeSource); } else { SourceRange range = rangeStartLength(variable, 0); addReplaceEdit(range, typeSource + " "); } } // add proposal addUnitCorrectionProposal(QA_ADD_TYPE_ANNOTATION); } void addProposal_assignToLocalVariable() throws Exception { // prepare enclosing ExpressionStatement Statement statement = node.getAncestor(Statement.class); if (!(statement instanceof ExpressionStatement)) { return; } ExpressionStatement expressionStatement = (ExpressionStatement) statement; // prepare expression Expression expression = expressionStatement.getExpression(); int offset = expression.getOffset(); // ignore if already assignment if (expression instanceof AssignmentExpression) { return; } // ignore "throw" if (expression instanceof ThrowExpression) { return; } // prepare expression type Type type = expression.getBestType(); if (type.isVoid()) { return; } // prepare source SourceBuilder builder = new SourceBuilder(offset); builder.append("var "); // prepare excluded names Set<String> excluded = Sets.newHashSet(); { ScopedNameFinder scopedNameFinder = new ScopedNameFinder(offset); expression.accept(scopedNameFinder); excluded.addAll(scopedNameFinder.getLocals().keySet()); } // name(s) { String[] suggestions = CorrectionUtils.getVariableNameSuggestions(type, expression, excluded); builder.startPosition("NAME"); for (int i = 0; i < suggestions.length; i++) { String name = suggestions[i]; if (i == 0) { builder.append(name); } builder.addProposal(CorrectionImage.IMG_CORRECTION_CLASS, name); } builder.endPosition(); } builder.append(" = "); // add proposal addInsertEdit(builder); addUnitCorrectionProposal(QA_ASSIGN_TO_LOCAL_VARIABLE); } void addProposal_convertToBlockFunctionBody() throws Exception { FunctionBody body = getEnclosingFunctionBody(); // prepare expression body if (!(body instanceof ExpressionFunctionBody)) { return; } Expression returnValue = ((ExpressionFunctionBody) body).getExpression(); // prepare prefix String prefix; { AstNode bodyParent = body.getParent(); prefix = utils.getNodePrefix(bodyParent); } // add change String eol = utils.getEndOfLine(); String indent = utils.getIndent(1); String newBodySource = "{" + eol + prefix + indent + "return " + getSource(returnValue) + ";" + eol + prefix + "}"; addReplaceEdit(rangeNode(body), newBodySource); // add proposal addUnitCorrectionProposal(QA_CONVERT_INTO_BLOCK_BODY); } void addProposal_convertToExpressionFunctionBody() throws Exception { // prepare current body FunctionBody body = getEnclosingFunctionBody(); if (!(body instanceof BlockFunctionBody)) { return; } // prepare return statement List<Statement> statements = ((BlockFunctionBody) body).getBlock().getStatements(); if (statements.size() != 1) { return; } if (!(statements.get(0) instanceof ReturnStatement)) { return; } ReturnStatement returnStatement = (ReturnStatement) statements.get(0); // prepare returned expression Expression returnExpression = returnStatement.getExpression(); if (returnExpression == null) { return; } // add change String newBodySource = "=> " + getSource(returnExpression); if (!(body.getParent() instanceof FunctionExpression) || body.getParent().getParent() instanceof FunctionDeclaration) { newBodySource += ";"; } addReplaceEdit(rangeNode(body), newBodySource); // add proposal addUnitCorrectionProposal(QA_CONVERT_INTO_EXPRESSION_BODY); } void addProposal_convertToIsNot_onIs() throws Exception { // may be child of "is" AstNode node = this.node; while (node != null && !(node instanceof IsExpression)) { node = node.getParent(); } // prepare "is" if (!(node instanceof IsExpression)) { return; } IsExpression isExpression = (IsExpression) node; if (isExpression.getNotOperator() != null) { return; } // prepare enclosing () AstNode parent = isExpression.getParent(); if (!(parent instanceof ParenthesizedExpression)) { return; } ParenthesizedExpression parExpression = (ParenthesizedExpression) parent; // prepare enclosing !() AstNode parent2 = parent.getParent(); if (!(parent2 instanceof PrefixExpression)) { return; } PrefixExpression prefExpression = (PrefixExpression) parent2; if (prefExpression.getOperator().getType() != TokenType.BANG) { return; } // strip !() if (CorrectionUtils.getParentPrecedence(prefExpression) >= TokenType.IS.getPrecedence()) { addRemoveEdit(rangeToken(prefExpression.getOperator())); } else { addRemoveEdit(rangeStartEnd(prefExpression, parExpression.getLeftParenthesis())); addRemoveEdit(rangeStartEnd(parExpression.getRightParenthesis(), prefExpression)); } addInsertEdit(isExpression.getIsOperator().getEnd(), "!"); // add proposal addUnitCorrectionProposal(QA_CONVERT_INTO_IS_NOT); } void addProposal_convertToIsNot_onNot() throws Exception { // may be () in prefix expression if (node instanceof ParenthesizedExpression && node.getParent() instanceof PrefixExpression) { node = node.getParent(); } // prepare !() if (!(node instanceof PrefixExpression)) { return; } PrefixExpression prefExpression = (PrefixExpression) node; // should be ! operator if (prefExpression.getOperator().getType() != TokenType.BANG) { return; } // prepare !() Expression operand = prefExpression.getOperand(); if (!(operand instanceof ParenthesizedExpression)) { return; } ParenthesizedExpression parExpression = (ParenthesizedExpression) operand; operand = parExpression.getExpression(); // prepare "is" if (!(operand instanceof IsExpression)) { return; } IsExpression isExpression = (IsExpression) operand; if (isExpression.getNotOperator() != null) { return; } // strip !() if (CorrectionUtils.getParentPrecedence(prefExpression) >= TokenType.IS.getPrecedence()) { addRemoveEdit(rangeToken(prefExpression.getOperator())); } else { addRemoveEdit(rangeStartEnd(prefExpression, parExpression.getLeftParenthesis())); addRemoveEdit(rangeStartEnd(parExpression.getRightParenthesis(), prefExpression)); } addInsertEdit(isExpression.getIsOperator().getEnd(), "!"); // add proposal addUnitCorrectionProposal(QA_CONVERT_INTO_IS_NOT); } /** * Converts "!isEmpty" -> "isNotEmpty" if possible. */ void addProposal_convertToIsNotEmpty() throws Exception { // prepare "expr.isEmpty" AstNode isEmptyAccess = null; SimpleIdentifier isEmptyIdentifier = null; if (node instanceof SimpleIdentifier) { SimpleIdentifier identifier = (SimpleIdentifier) node; AstNode parent = identifier.getParent(); // normal case (but rare) if (parent instanceof PropertyAccess) { PropertyAccess propertyAccess = (PropertyAccess) parent; isEmptyIdentifier = propertyAccess.getPropertyName(); isEmptyAccess = propertyAccess; } // usual case if (parent instanceof PrefixedIdentifier) { PrefixedIdentifier prefixedIdentifier = (PrefixedIdentifier) parent; isEmptyIdentifier = prefixedIdentifier.getIdentifier(); isEmptyAccess = prefixedIdentifier; } } if (isEmptyIdentifier == null) { return; } // should be "isEmpty" Element propertyElement = isEmptyIdentifier.getBestElement(); if (propertyElement == null || !"isEmpty".equals(propertyElement.getName())) { return; } // should have "isNotEmpty" Element propertyTarget = propertyElement.getEnclosingElement(); if (propertyTarget == null || CorrectionUtils.getChildren(propertyTarget, "isNotEmpty").isEmpty()) { return; } // should be in PrefixExpression if (!(isEmptyAccess.getParent() instanceof PrefixExpression)) { return; } PrefixExpression prefixExpression = (PrefixExpression) isEmptyAccess.getParent(); // should be ! if (prefixExpression.getOperator().getType() != TokenType.BANG) { return; } // do replace addRemoveEdit(rangeStartStart(prefixExpression, prefixExpression.getOperand())); addReplaceEdit(rangeNode(isEmptyIdentifier), "isNotEmpty"); // add proposal addUnitCorrectionProposal(QA_CONVERT_INTO_IS_NOT_EMPTY); } void addProposal_exchangeOperands() throws Exception { // check that user invokes quick assist on binary expression if (!(node instanceof BinaryExpression)) { return; } BinaryExpression binaryExpression = (BinaryExpression) node; // prepare operator position int offset = isOperatorSelected(binaryExpression, selectionOffset, selectionLength); if (offset == -1) { return; } // add edits { Expression leftOperand = binaryExpression.getLeftOperand(); Expression rightOperand = binaryExpression.getRightOperand(); // find "wide" enclosing binary expression with same operator while (binaryExpression.getParent() instanceof BinaryExpression) { BinaryExpression newBinaryExpression = (BinaryExpression) binaryExpression.getParent(); if (newBinaryExpression.getOperator().getType() != binaryExpression.getOperator().getType()) { break; } binaryExpression = newBinaryExpression; } // exchange parts of "wide" expression parts SourceRange leftRange = rangeStartEnd(binaryExpression, leftOperand); SourceRange rightRange = rangeStartEnd(rightOperand, binaryExpression); addReplaceEdit(leftRange, getSource(rightRange)); addReplaceEdit(rightRange, getSource(leftRange)); } // add proposal addUnitCorrectionProposal(QA_EXCHANGE_OPERANDS); } void addProposal_extractClassIntoPart() throws Exception { // should be on the name if (!(node instanceof SimpleIdentifier)) { return; } if (!(node.getParent() instanceof ClassDeclaration)) { return; } ClassDeclaration classDeclaration = (ClassDeclaration) node.getParent(); SourceRange linesRange = utils.getLinesRange(rangeNode(classDeclaration)); // prepare name String className = classDeclaration.getName().getName(); String fileName = CorrectionUtils.getRecommentedFileNameForClass(className); // prepare new file File newFile = new File(unitLibraryFolder, fileName); if (newFile.exists()) { return; } // remove class from this unit SourceChange unitChange = new SourceChange(source.getShortName(), source); unitChange.addEdit(new Edit(linesRange, "")); // create new unit Change createFileChange; { String newContent = "part of " + unitLibraryElement.getDisplayName() + ";"; newContent += utils.getEndOfLine(); newContent += utils.getEndOfLine(); newContent += getSource(linesRange); createFileChange = new CreateFileChange(fileName, newFile, newContent); } // add 'part' SourceChange libraryChange = getInsertPartDirectiveChange(unitLibrarySource, fileName); // add proposal Change compositeChange = new CompositeChange("", unitChange, createFileChange, libraryChange); proposals.add(new ChangeCorrectionProposal(compositeChange, QA_EXTRACT_CLASS, fileName)); } void addProposal_importAddShow() throws Exception { // prepare ImportDirective ImportDirective importDirective = node.getAncestor(ImportDirective.class); if (importDirective == null) { return; } // there should be no existing combinators if (!importDirective.getCombinators().isEmpty()) { return; } // prepare whole import namespace ImportElement importElement = importDirective.getElement(); Map<String, Element> namespace = CorrectionUtils.getImportNamespace(importElement); // prepare names of referenced elements (from this import) Set<String> referencedNames = Sets.newTreeSet(); SearchEngine searchEngine = assistContext.getSearchEngine(); for (Element element : namespace.values()) { List<SearchMatch> references = searchEngine.searchReferences(element, null, null); for (SearchMatch match : references) { LibraryElement library = match.getElement().getLibrary(); if (unitLibraryElement.equals(library)) { referencedNames.add(element.getDisplayName()); break; } } } // ignore if unused if (referencedNames.isEmpty()) { return; } // prepare change String sb = " show " + StringUtils.join(referencedNames, ", "); addInsertEdit(importDirective.getEnd() - 1, sb.toString()); // add proposal addUnitCorrectionProposal(QA_IMPORT_ADD_SHOW); } void addProposal_invertIf() throws Exception { if (!(node instanceof IfStatement)) { return; } IfStatement ifStatement = (IfStatement) node; Expression condition = ifStatement.getCondition(); // should have both "then" and "else" Statement thenStatement = ifStatement.getThenStatement(); Statement elseStatement = ifStatement.getElseStatement(); if (thenStatement == null || elseStatement == null) { return; } // prepare source String invertedCondition = utils.invertCondition(condition); String thenSource = getSource(thenStatement); String elseSource = getSource(elseStatement); // do replacements addReplaceEdit(rangeNode(condition), invertedCondition); addReplaceEdit(rangeNode(thenStatement), elseSource); addReplaceEdit(rangeNode(elseStatement), thenSource); // add proposal addUnitCorrectionProposal(QA_INVERT_IF_STATEMENT); } void addProposal_joinIfStatementInner() throws Exception { // climb up condition to the (supposedly) "if" statement AstNode node = this.node; while (node instanceof Expression) { node = node.getParent(); } // prepare target "if" statement if (!(node instanceof IfStatement)) { return; } IfStatement targetIfStatement = (IfStatement) node; if (targetIfStatement.getElseStatement() != null) { return; } // prepare inner "if" statement Statement targetThenStatement = targetIfStatement.getThenStatement(); Statement innerStatement = CorrectionUtils.getSingleStatement(targetThenStatement); if (!(innerStatement instanceof IfStatement)) { return; } IfStatement innerIfStatement = (IfStatement) innerStatement; if (innerIfStatement.getElseStatement() != null) { return; } // prepare environment String prefix = utils.getNodePrefix(targetIfStatement); String eol = utils.getEndOfLine(); // merge conditions String condition; { Expression targetCondition = targetIfStatement.getCondition(); Expression innerCondition = innerIfStatement.getCondition(); String targetConditionSource = getSource(targetCondition); String innerConditionSource = getSource(innerCondition); if (shouldWrapParenthesisBeforeAnd(targetCondition)) { targetConditionSource = "(" + targetConditionSource + ")"; } if (shouldWrapParenthesisBeforeAnd(innerCondition)) { innerConditionSource = "(" + innerConditionSource + ")"; } condition = targetConditionSource + " && " + innerConditionSource; } // replace target "if" statement { Statement innerThenStatement = innerIfStatement.getThenStatement(); List<Statement> innerThenStatements = CorrectionUtils.getStatements(innerThenStatement); SourceRange lineRanges = utils.getLinesRange(innerThenStatements); String oldSource = utils.getText(lineRanges); String newSource = utils.getIndentSource(oldSource, false); addReplaceEdit( rangeNode(targetIfStatement), MessageFormat.format("if ({0}) '{'{1}{2}{3}'}'", condition, eol, newSource, prefix)); } // done addUnitCorrectionProposal(QA_JOIN_IF_WITH_INNER); } void addProposal_joinIfStatementOuter() throws Exception { // climb up condition to the (supposedly) "if" statement AstNode node = this.node; while (node instanceof Expression) { node = node.getParent(); } // prepare target "if" statement if (!(node instanceof IfStatement)) { return; } IfStatement targetIfStatement = (IfStatement) node; if (targetIfStatement.getElseStatement() != null) { return; } // prepare outer "if" statement AstNode parent = targetIfStatement.getParent(); if (parent instanceof Block) { parent = parent.getParent(); } if (!(parent instanceof IfStatement)) { return; } IfStatement outerIfStatement = (IfStatement) parent; if (outerIfStatement.getElseStatement() != null) { return; } // prepare environment String prefix = utils.getNodePrefix(outerIfStatement); String eol = utils.getEndOfLine(); // merge conditions String condition; { Expression targetCondition = targetIfStatement.getCondition(); Expression outerCondition = outerIfStatement.getCondition(); String targetConditionSource = getSource(targetCondition); String outerConditionSource = getSource(outerCondition); if (shouldWrapParenthesisBeforeAnd(targetCondition)) { targetConditionSource = "(" + targetConditionSource + ")"; } if (shouldWrapParenthesisBeforeAnd(outerCondition)) { outerConditionSource = "(" + outerConditionSource + ")"; } condition = outerConditionSource + " && " + targetConditionSource; } // replace outer "if" statement { Statement targetThenStatement = targetIfStatement.getThenStatement(); List<Statement> targetThenStatements = CorrectionUtils.getStatements(targetThenStatement); SourceRange lineRanges = utils.getLinesRange(targetThenStatements); String oldSource = utils.getText(lineRanges); String newSource = utils.getIndentSource(oldSource, false); addReplaceEdit( rangeNode(outerIfStatement), MessageFormat.format("if ({0}) '{'{1}{2}{3}'}'", condition, eol, newSource, prefix)); } // done addUnitCorrectionProposal(QA_JOIN_IF_WITH_OUTER); } void addProposal_joinVariableDeclaration_onAssignment() throws Exception { // check that node is LHS in assignment if (node instanceof SimpleIdentifier && node.getParent() instanceof AssignmentExpression && ((AssignmentExpression) node.getParent()).getLeftHandSide() == node && node.getParent().getParent() instanceof ExpressionStatement) { } else { return; } AssignmentExpression assignExpression = (AssignmentExpression) node.getParent(); // check that binary expression is assignment if (assignExpression.getOperator().getType() != TokenType.EQ) { return; } // prepare "declaration" statement Element element = ((SimpleIdentifier) node).getStaticElement(); if (element == null) { return; } int declOffset = element.getNameOffset(); AstNode declNode = new NodeLocator(declOffset).searchWithin(unit); if (declNode != null && declNode.getParent() instanceof VariableDeclaration && ((VariableDeclaration) declNode.getParent()).getName() == declNode && declNode.getParent().getParent() instanceof VariableDeclarationList && declNode.getParent().getParent().getParent() instanceof VariableDeclarationStatement) { } else { return; } VariableDeclaration decl = (VariableDeclaration) declNode.getParent(); VariableDeclarationStatement declStatement = (VariableDeclarationStatement) decl.getParent().getParent(); // may be has initializer if (decl.getInitializer() != null) { return; } // check that "declaration" statement declared only one variable if (declStatement.getVariables().getVariables().size() != 1) { return; } // check that "declaration" and "assignment" statements are part of same Block ExpressionStatement assignStatement = (ExpressionStatement) node.getParent().getParent(); if (assignStatement.getParent() instanceof Block && assignStatement.getParent() == declStatement.getParent()) { } else { return; } Block block = (Block) assignStatement.getParent(); // check that "declaration" and "assignment" statements are adjacent List<Statement> statements = block.getStatements(); if (statements.indexOf(assignStatement) == statements.indexOf(declStatement) + 1) { } else { return; } // add edits { int assignOffset = assignExpression.getOperator().getOffset(); addReplaceEdit(rangeEndStart(declNode, assignOffset), " "); } // add proposal addUnitCorrectionProposal(QA_JOIN_VARIABLE_DECLARATION); } void addProposal_joinVariableDeclaration_onDeclaration() throws Exception { // prepare enclosing VariableDeclarationList VariableDeclarationList declList = node.getAncestor(VariableDeclarationList.class); if (declList != null && declList.getVariables().size() == 1) { } else { return; } VariableDeclaration decl = declList.getVariables().get(0); // already initialized if (decl.getInitializer() != null) { return; } // prepare VariableDeclarationStatement in Block if (declList.getParent() instanceof VariableDeclarationStatement && declList.getParent().getParent() instanceof Block) { } else { return; } VariableDeclarationStatement declStatement = (VariableDeclarationStatement) declList.getParent(); Block block = (Block) declStatement.getParent(); List<Statement> statements = block.getStatements(); // prepare assignment AssignmentExpression assignExpression; { // declaration should not be last Statement int declIndex = statements.indexOf(declStatement); if (declIndex < statements.size() - 1) { } else { return; } // next Statement should be assignment Statement assignStatement = statements.get(declIndex + 1); if (assignStatement instanceof ExpressionStatement) { } else { return; } ExpressionStatement expressionStatement = (ExpressionStatement) assignStatement; // expression should be assignment if (expressionStatement.getExpression() instanceof AssignmentExpression) { } else { return; } assignExpression = (AssignmentExpression) expressionStatement.getExpression(); } // check that pure assignment if (assignExpression.getOperator().getType() != TokenType.EQ) { return; } // add edits { int assignOffset = assignExpression.getOperator().getOffset(); addReplaceEdit(rangeEndStart(decl.getName(), assignOffset), " "); } // add proposal addUnitCorrectionProposal(QA_JOIN_VARIABLE_DECLARATION); } void addProposal_removeTypeAnnotation() throws Exception { AstNode typeStart = null; AstNode typeEnd = null; // try top-level variable { TopLevelVariableDeclaration declaration = node.getAncestor(TopLevelVariableDeclaration.class); if (declaration != null) { TypeName typeNode = declaration.getVariables().getType(); if (typeNode != null) { VariableDeclaration field = declaration.getVariables().getVariables().get(0); typeStart = declaration; typeEnd = field; } } } // try class field { FieldDeclaration fieldDeclaration = node.getAncestor(FieldDeclaration.class); if (fieldDeclaration != null) { TypeName typeNode = fieldDeclaration.getFields().getType(); if (typeNode != null) { VariableDeclaration field = fieldDeclaration.getFields().getVariables().get(0); typeStart = fieldDeclaration; typeEnd = field; } } } // try local variable { VariableDeclarationStatement statement = node.getAncestor(VariableDeclarationStatement.class); if (statement != null) { TypeName typeNode = statement.getVariables().getType(); if (typeNode != null) { VariableDeclaration variable = statement.getVariables().getVariables().get(0); typeStart = typeNode; typeEnd = variable; } } } // add edit if (typeStart != null && typeEnd != null) { SourceRange typeRange = rangeStartStart(typeStart, typeEnd); addReplaceEdit(typeRange, "var "); } // add proposal addUnitCorrectionProposal(QA_REMOVE_TYPE_ANNOTATION); } void addProposal_replaceConditionalWithIfElse() throws Exception { ConditionalExpression conditional = null; // may be on Statement with Conditional Statement statement = node.getAncestor(Statement.class); if (statement == null) { return; } // variable declaration boolean inVariable = false; if (statement instanceof VariableDeclarationStatement) { VariableDeclarationStatement variableStatement = (VariableDeclarationStatement) statement; for (VariableDeclaration variable : variableStatement.getVariables().getVariables()) { if (variable.getInitializer() instanceof ConditionalExpression) { conditional = (ConditionalExpression) variable.getInitializer(); inVariable = true; break; } } } // assignment boolean inAssignment = false; if (statement instanceof ExpressionStatement) { ExpressionStatement exprStmt = (ExpressionStatement) statement; if (exprStmt.getExpression() instanceof AssignmentExpression) { AssignmentExpression assignment = (AssignmentExpression) exprStmt.getExpression(); if (assignment.getOperator().getType() == TokenType.EQ && assignment.getRightHandSide() instanceof ConditionalExpression) { conditional = (ConditionalExpression) assignment.getRightHandSide(); inAssignment = true; } } } // return boolean inReturn = false; if (statement instanceof ReturnStatement) { ReturnStatement returnStatement = (ReturnStatement) statement; if (returnStatement.getExpression() instanceof ConditionalExpression) { conditional = (ConditionalExpression) returnStatement.getExpression(); inReturn = true; } } // prepare environment String eol = utils.getEndOfLine(); String indent = utils.getIndent(1); String prefix = utils.getNodePrefix(statement); // Type v = Conditional; if (inVariable) { VariableDeclaration variable = (VariableDeclaration) conditional.getParent(); addRemoveEdit(rangeEndEnd(variable.getName(), conditional)); addReplaceEdit(rangeEndLength(statement, 0), MessageFormat.format( "{3}{4}if ({0}) '{'{3}{4}{5}{6} = {1};{3}{4}'} else {'{3}{4}{5}{6} = {2};{3}{4}'}'", getSource(conditional.getCondition()), getSource(conditional.getThenExpression()), getSource(conditional.getElseExpression()), eol, prefix, indent, variable.getName())); } // v = Conditional; if (inAssignment) { AssignmentExpression assignment = (AssignmentExpression) conditional.getParent(); Expression leftSide = assignment.getLeftHandSide(); addReplaceEdit(rangeNode(statement), MessageFormat.format( "if ({0}) '{'{3}{4}{5}{6} = {1};{3}{4}'} else {'{3}{4}{5}{6} = {2};{3}{4}'}'", getSource(conditional.getCondition()), getSource(conditional.getThenExpression()), getSource(conditional.getElseExpression()), eol, prefix, indent, getSource(leftSide))); } // return Conditional; if (inReturn) { addReplaceEdit(rangeNode(statement), MessageFormat.format( "if ({0}) '{'{3}{4}{5}return {1};{3}{4}'} else {'{3}{4}{5}return {2};{3}{4}'}'", getSource(conditional.getCondition()), getSource(conditional.getThenExpression()), getSource(conditional.getElseExpression()), eol, prefix, indent)); } // add proposal addUnitCorrectionProposal(QA_REPLACE_CONDITIONAL_WITH_IF_ELSE); } void addProposal_replaceIfElseWithConditional() throws Exception { // should be "if" if (!(node instanceof IfStatement)) { return; } IfStatement ifStatement = (IfStatement) node; // single then/else statements Statement thenStatement = CorrectionUtils.getSingleStatement(ifStatement.getThenStatement()); Statement elseStatement = CorrectionUtils.getSingleStatement(ifStatement.getElseStatement()); if (thenStatement == null || elseStatement == null) { return; } // returns if (thenStatement instanceof ReturnStatement || elseStatement instanceof ReturnStatement) { ReturnStatement thenReturn = (ReturnStatement) thenStatement; ReturnStatement elseReturn = (ReturnStatement) elseStatement; addReplaceEdit(rangeNode(ifStatement), MessageFormat.format( "return {0} ? {1} : {2};", getSource(ifStatement.getCondition()), getSource(thenReturn.getExpression()), getSource(elseReturn.getExpression()))); } // assignments -> v = Conditional; if (thenStatement instanceof ExpressionStatement && elseStatement instanceof ExpressionStatement) { Expression thenExpression = ((ExpressionStatement) thenStatement).getExpression(); Expression elseExpression = ((ExpressionStatement) elseStatement).getExpression(); if (thenExpression instanceof AssignmentExpression && elseExpression instanceof AssignmentExpression) { AssignmentExpression thenAssignment = (AssignmentExpression) thenExpression; AssignmentExpression elseAssignment = (AssignmentExpression) elseExpression; String thenTarget = getSource(thenAssignment.getLeftHandSide()); String elseTarget = getSource(elseAssignment.getLeftHandSide()); if (thenAssignment.getOperator().getType() == TokenType.EQ && elseAssignment.getOperator().getType() == TokenType.EQ && StringUtils.equals(thenTarget, elseTarget)) { addReplaceEdit( rangeNode(ifStatement), MessageFormat.format( "{0} = {1} ? {2} : {3};", thenTarget, getSource(ifStatement.getCondition()), getSource(thenAssignment.getRightHandSide()), getSource(elseAssignment.getRightHandSide()))); } } } // add proposal addUnitCorrectionProposal(QA_REPLACE_IF_ELSE_WITH_CONDITIONAL); } void addProposal_splitAndCondition() throws Exception { // check that user invokes quick assist on binary expression if (!(node instanceof BinaryExpression)) { return; } BinaryExpression binaryExpression = (BinaryExpression) node; // prepare operator position int offset = isOperatorSelected(binaryExpression, selectionOffset, selectionLength); if (offset == -1) { return; } // should be && if (binaryExpression.getOperator().getType() != TokenType.AMPERSAND_AMPERSAND) { return; } // prepare "if" Statement statement = node.getAncestor(Statement.class); if (!(statement instanceof IfStatement)) { return; } IfStatement ifStatement = (IfStatement) statement; // check that binary expression is part of first level && condition of "if" BinaryExpression condition = binaryExpression; while (condition.getParent() instanceof BinaryExpression && ((BinaryExpression) condition.getParent()).getOperator().getType() == TokenType.AMPERSAND_AMPERSAND) { condition = (BinaryExpression) condition.getParent(); } if (ifStatement.getCondition() != condition) { return; } // prepare environment String prefix = utils.getNodePrefix(ifStatement); String eol = utils.getEndOfLine(); String indent = utils.getIndent(1); // prepare "rightCondition" String rightConditionSource; { SourceRange rightConditionRange = rangeStartEnd(binaryExpression.getRightOperand(), condition); rightConditionSource = getSource(rightConditionRange); } // remove "&& rightCondition" addRemoveEdit(rangeEndEnd(binaryExpression.getLeftOperand(), condition)); // update "then" statement Statement thenStatement = ifStatement.getThenStatement(); Statement elseStatement = ifStatement.getElseStatement(); if (thenStatement instanceof Block) { Block thenBlock = (Block) thenStatement; SourceRange thenBlockRange = rangeNode(thenBlock); // insert inner "if" with right part of "condition" { String source = eol + prefix + indent + "if (" + rightConditionSource + ") {"; int thenBlockInsideOffset = thenBlockRange.getOffset() + 1; addInsertEdit(thenBlockInsideOffset, source); } // insert closing "}" for inner "if" { int thenBlockEnd = thenBlockRange.getEnd(); String source = indent + "}"; // may be move "else" statements if (elseStatement != null) { List<Statement> elseStatements = CorrectionUtils.getStatements(elseStatement); SourceRange elseLinesRange = utils.getLinesRange(elseStatements); String elseIndentOld = prefix + indent; String elseIndentNew = elseIndentOld + indent; String newElseSource = utils.getIndentSource(elseLinesRange, elseIndentOld, elseIndentNew); // append "else" block source += " else {" + eol; source += newElseSource; source += prefix + indent + "}"; // remove old "else" range addRemoveEdit(rangeStartEnd(thenBlockEnd, elseStatement)); } // insert before outer "then" block "}" source += eol + prefix; addInsertEdit(thenBlockEnd - 1, source); } } else { // insert inner "if" with right part of "condition" { String source = eol + prefix + indent + "if (" + rightConditionSource + ")"; addInsertEdit(ifStatement.getRightParenthesis().getOffset() + 1, source); } // indent "else" statements to correspond inner "if" if (elseStatement != null) { SourceRange elseRange = rangeStartEnd( ifStatement.getElseKeyword().getOffset(), elseStatement); SourceRange elseLinesRange = utils.getLinesRange(elseRange); String elseIndentOld = prefix; String elseIndentNew = elseIndentOld + indent; textEdits.add(utils.createIndentEdit(elseLinesRange, elseIndentOld, elseIndentNew)); } } // indent "then" statements to correspond inner "if" { List<Statement> thenStatements = CorrectionUtils.getStatements(thenStatement); SourceRange linesRange = utils.getLinesRange(thenStatements); String thenIndentOld = prefix + indent; String thenIndentNew = thenIndentOld + indent; textEdits.add(utils.createIndentEdit(linesRange, thenIndentOld, thenIndentNew)); } // add proposal addUnitCorrectionProposal(QA_SPLIT_AND_CONDITION); } void addProposal_splitVariableDeclaration() throws Exception { // prepare DartVariableStatement, should be part of Block VariableDeclarationStatement statement = node.getAncestor(VariableDeclarationStatement.class); if (statement != null && statement.getParent() instanceof Block) { } else { return; } // check that statement declares single variable List<VariableDeclaration> variables = statement.getVariables().getVariables(); if (variables.size() != 1) { return; } VariableDeclaration variable = variables.get(0); // remove initializer value addRemoveEdit(rangeEndStart(variable.getName(), statement.getSemicolon())); // add assignment statement String eol = utils.getEndOfLine(); String indent = utils.getNodePrefix(statement); String assignSource = MessageFormat.format( "{0} = {1};", variable.getName().getName(), getSource(variable.getInitializer())); SourceRange assignRange = rangeEndLength(statement, 0); addReplaceEdit(assignRange, eol + indent + assignSource); // add proposal addUnitCorrectionProposal(QA_SPLIT_VARIABLE_DECLARATION); } void addProposal_surroundWith() throws Exception { // prepare selected statements List<Statement> selectedStatements; { SourceRange selection = rangeStartLength(selectionOffset, selectionLength); StatementAnalyzer selectionAnalyzer = new StatementAnalyzer(unit, selection); unit.accept(selectionAnalyzer); List<AstNode> selectedNodes = selectionAnalyzer.getSelectedNodes(); // convert nodes to statements selectedStatements = Lists.newArrayList(); for (AstNode selectedNode : selectedNodes) { if (selectedNode instanceof Statement) { selectedStatements.add((Statement) selectedNode); } } // we want only statements if (selectedStatements.isEmpty() || selectedStatements.size() != selectedNodes.size()) { return; } } // prepare statement information Statement firstStatement = selectedStatements.get(0); Statement lastStatement = selectedStatements.get(selectedStatements.size() - 1); SourceRange statementsRange = utils.getLinesRange(selectedStatements); // prepare environment String eol = utils.getEndOfLine(); String indentOld = utils.getNodePrefix(firstStatement); String indentNew = indentOld + utils.getIndent(1); // "block" { addInsertEdit(statementsRange.getOffset(), indentOld + "{" + eol); { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } addInsertEdit(statementsRange.getEnd(), indentOld + "}" + eol); proposalEndRange = rangeEndLength(lastStatement, 0); // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_BLOCK); } // "if" { { int offset = statementsRange.getOffset(); SourceBuilder sb = new SourceBuilder(offset); sb.append(indentOld); sb.append("if ("); { sb.startPosition("CONDITION"); sb.append("condition"); sb.endPosition(); } sb.append(") {"); sb.append(eol); addInsertEdit(sb); } { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } addInsertEdit(statementsRange.getEnd(), indentOld + "}" + eol); proposalEndRange = rangeEndLength(lastStatement, 0); // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_IF); } // "while" { { int offset = statementsRange.getOffset(); SourceBuilder sb = new SourceBuilder(offset); sb.append(indentOld); sb.append("while ("); { sb.startPosition("CONDITION"); sb.append("condition"); sb.endPosition(); } sb.append(") {"); sb.append(eol); addInsertEdit(sb); } { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } addInsertEdit(statementsRange.getEnd(), indentOld + "}" + eol); proposalEndRange = rangeEndLength(lastStatement, 0); // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_WHILE); } // "for-in" { { int offset = statementsRange.getOffset(); SourceBuilder sb = new SourceBuilder(offset); sb.append(indentOld); sb.append("for (var "); { sb.startPosition("NAME"); sb.append("item"); sb.endPosition(); } sb.append(" in "); { sb.startPosition("ITERABLE"); sb.append("iterable"); sb.endPosition(); } sb.append(") {"); sb.append(eol); addInsertEdit(sb); } { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } addInsertEdit(statementsRange.getEnd(), indentOld + "}" + eol); proposalEndRange = rangeEndLength(lastStatement, 0); // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_FOR_IN); } // "for" { { int offset = statementsRange.getOffset(); SourceBuilder sb = new SourceBuilder(offset); sb.append(indentOld); sb.append("for (var "); { sb.startPosition("VAR"); sb.append("v"); sb.endPosition(); } sb.append(" = "); { sb.startPosition("INIT"); sb.append("init"); sb.endPosition(); } sb.append("; "); { sb.startPosition("CONDITION"); sb.append("condition"); sb.endPosition(); } sb.append("; "); { sb.startPosition("INCREMENT"); sb.append("increment"); sb.endPosition(); } sb.append(") {"); sb.append(eol); addInsertEdit(sb); } { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } addInsertEdit(statementsRange.getEnd(), indentOld + "}" + eol); proposalEndRange = rangeEndLength(lastStatement, 0); // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_FOR); } // "do-while" { addInsertEdit(statementsRange.getOffset(), indentOld + "do {" + eol); { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } { int offset = statementsRange.getEnd(); SourceBuilder sb = new SourceBuilder(offset); sb.append(indentOld); sb.append("} while ("); { sb.startPosition("CONDITION"); sb.append("condition"); sb.endPosition(); } sb.append(");"); sb.append(eol); addInsertEdit(sb); } proposalEndRange = rangeEndLength(lastStatement, 0); // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_DO_WHILE); } // "try-catch" { addInsertEdit(statementsRange.getOffset(), indentOld + "try {" + eol); { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } { int offset = statementsRange.getEnd(); SourceBuilder sb = new SourceBuilder(offset); sb.append(indentOld); sb.append("} on "); { sb.startPosition("EXCEPTION_TYPE"); sb.append("Exception"); sb.endPosition(); } sb.append(" catch ("); { sb.startPosition("EXCEPTION_VAR"); sb.append("e"); sb.endPosition(); } sb.append(") {"); sb.append(eol); // sb.append(indentNew); { sb.startPosition("CATCH"); sb.append("// TODO"); sb.endPosition(); sb.setEndPosition(); } sb.append(eol); // sb.append(indentOld); sb.append("}"); sb.append(eol); // addInsertEdit(sb); } // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_TRY_CATCH); } // "try-finally" { addInsertEdit(statementsRange.getOffset(), indentOld + "try {" + eol); { Edit edit = utils.createIndentEdit(statementsRange, indentOld, indentNew); textEdits.add(edit); } { int offset = statementsRange.getEnd(); SourceBuilder sb = new SourceBuilder(offset); // sb.append(indentOld); sb.append("} finally {"); sb.append(eol); // sb.append(indentNew); { sb.startPosition("FINALLY"); sb.append("// TODO"); sb.endPosition(); } sb.setEndPosition(); sb.append(eol); // sb.append(indentOld); sb.append("}"); sb.append(eol); // addInsertEdit(sb); } // add proposal addUnitCorrectionProposal(QA_SURROUND_WITH_TRY_FINALLY); } } // private void addLinkedPositionProposal(String group, CorrectionImage icon, String text) { // List<TrackedNodeProposal> nodeProposals = linkedPositionProposals.get(group); // if (nodeProposals == null) { // nodeProposals = Lists.newArrayList(); // linkedPositionProposals.put(group, nodeProposals); // } // nodeProposals.add(new TrackedNodeProposal(icon, text)); // } private void addInsertEdit(int offset, String text) { textEdits.add(createInsertEdit(offset, text)); } private void addInsertEdit(SourceBuilder builder) { int offset = builder.getOffset(); Edit edit = createInsertEdit(offset, builder.toString()); textEdits.add(edit); addLinkedPositions(builder, edit); } private void addLinkedPosition(String group, SourceRange range) { List<SourceRange> positions = linkedPositions.get(group); if (positions == null) { positions = Lists.newArrayList(); linkedPositions.put(group, positions); } positions.add(range); } 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, Edit edit) { // end position { int endPosition = builder.getEndPosition(); if (endPosition != -1) { proposalEndRange = rangeStartLength(endPosition, 0); positionStopEdits.put(proposalEndRange, edit); } } // positions Map<String, List<SourceRange>> builderPositions = builder.getLinkedPositions(); for (Entry<String, List<SourceRange>> entry : builderPositions.entrySet()) { String groupId = entry.getKey(); for (SourceRange position : entry.getValue()) { addLinkedPosition(groupId, position); positionStopEdits.put(position, edit); } } // proposals for positions for (Entry<String, List<LinkedPositionProposal>> entry : builder.getLinkedProposals().entrySet()) { String group = entry.getKey(); for (LinkedPositionProposal proposal : entry.getValue()) { addLinkedPositionProposal(group, proposal); } } } /** * Adds {@link Edit} which removes source in the given {@link SourceRange}. */ private void addRemoveEdit(SourceRange range) { addReplaceEdit(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) { 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); // done proposals.add(proposal); } // reset resetProposalElements(); } private void addUnresolvedProposal_addPart() throws Exception { // should be PartOfDirective selected PartOfDirective partOfDirective = node.getAncestor(PartOfDirective.class); if (partOfDirective == null) { return; } LibraryIdentifier partOfNameIdentifier = partOfDirective.getLibraryName(); if (partOfNameIdentifier == null) { return; } String requiredLibraryName = partOfNameIdentifier.toString(); // prepare unit File File unitFile = QuickFixProcessorImpl.getSourceFile(source); if (unitFile == null) { return; } // check all libraries Source[] librarySources = analysisContext.getLibrarySources(); for (Source librarySource : librarySources) { LibraryElement libraryElement = analysisContext.getLibraryElement(librarySource); if (StringUtils.equals(libraryElement.getName(), requiredLibraryName)) { // prepare library File File libraryFile = QuickFixProcessorImpl.getSourceFile(librarySource); if (libraryFile == null) { continue; } // prepare relative URI URI libraryFolderUri = libraryFile.getParentFile().toURI(); URI unitUri = unitFile.toURI(); String relative = libraryFolderUri.relativize(unitUri).getPath(); SourceChange change = getInsertPartDirectiveChange(librarySource, relative); if (change == null) { continue; } proposals.add(new SourceCorrectionProposal(change, CorrectionKind.QA_ADD_PART_DIRECTIVE)); } } } private void cleanUp() { proposals.clear(); textEdits.clear(); positionStopEdits.clear(); linkedPositions.clear(); linkedPositionProposals.clear(); assistContext = null; analysisContext = null; source = null; unit = null; node = null; unitLibrarySource = null; unitLibraryElement = null; unitLibraryFile = null; unitLibraryFolder = null; utils = null; } private Edit createInsertEdit(int offset, String text) { return new Edit(offset, 0, text); } private FunctionBody getEnclosingFunctionBody() { { FunctionExpression function = node.getAncestor(FunctionExpression.class); if (function != null) { return function.getBody(); } } { FunctionDeclaration function = node.getAncestor(FunctionDeclaration.class); if (function != null) { return function.getFunctionExpression().getBody(); } } { MethodDeclaration method = node.getAncestor(MethodDeclaration.class); if (method != null) { return method.getBody(); } } return null; } /** * @return the {@link SourceChange} to insert "part" directive with given URI into the given * library. */ private SourceChange getInsertPartDirectiveChange(Source librarySource, String uri) throws Exception { // prepare library CompilationUnit CompilationUnit libraryUnit = analysisContext.getResolvedCompilationUnit( librarySource, librarySource); if (libraryUnit == null) { return null; } // prepare location for "part" directive utils = new CorrectionUtils(libraryUnit); InsertDesc insertDesc = utils.getInsertDescPart(); // build source to insert StringBuilder sb = new StringBuilder(); sb.append(insertDesc.prefix); sb.append("part '"); sb.append(uri); sb.append("';"); sb.append(insertDesc.suffix); // add proposal SourceChange change = new SourceChange(librarySource.getShortName(), librarySource); change.addEdit(new Edit(insertDesc.offset, 0, sb.toString())); return change; } /** * @return the part of {@link #unit} source. */ private String getSource(AstNode node) { return utils.getText(node); } /** * @return the part of {@link #unit} source. */ private String getSource(SourceRange range) { return utils.getText(range); } private void resetProposalElements() { textEdits.clear(); linkedPositions.clear(); positionStopEdits.clear(); linkedPositionProposals.clear(); proposalEndRange = null; } }