Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up
Find file
Copy path
Fetching contributors…
| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| library services.src.correction.assist; | |
| import 'dart:async'; | |
| import 'dart:collection'; | |
| import 'package:analysis_server/plugin/edit/assist/assist_core.dart'; | |
| import 'package:analysis_server/plugin/edit/assist/assist_dart.dart'; | |
| import 'package:analysis_server/src/protocol_server.dart' hide Element; | |
| import 'package:analysis_server/src/services/correction/assist.dart'; | |
| import 'package:analysis_server/src/services/correction/name_suggestion.dart'; | |
| import 'package:analysis_server/src/services/correction/source_buffer.dart'; | |
| import 'package:analysis_server/src/services/correction/source_range.dart'; | |
| import 'package:analysis_server/src/services/correction/statement_analyzer.dart'; | |
| import 'package:analysis_server/src/services/correction/util.dart'; | |
| import 'package:analysis_server/src/services/search/hierarchy.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/dart/element/type.dart'; | |
| import 'package:analyzer/src/dart/ast/token.dart'; | |
| import 'package:analyzer/src/dart/ast/utilities.dart'; | |
| import 'package:analyzer/src/generated/engine.dart'; | |
| import 'package:analyzer/src/generated/java_core.dart'; | |
| import 'package:analyzer/src/generated/source.dart'; | |
| import 'package:path/path.dart'; | |
| typedef _SimpleIdentifierVisitor(SimpleIdentifier node); | |
| /** | |
| * The computer for Dart assists. | |
| */ | |
| class AssistProcessor { | |
| AnalysisContext analysisContext; | |
| Source source; | |
| String file; | |
| int fileStamp; | |
| CompilationUnit unit; | |
| CompilationUnitElement unitElement; | |
| LibraryElement unitLibraryElement; | |
| String unitLibraryFile; | |
| String unitLibraryFolder; | |
| int selectionOffset; | |
| int selectionLength; | |
| int selectionEnd; | |
| final List<Assist> assists = <Assist>[]; | |
| final Map<String, LinkedEditGroup> linkedPositionGroups = | |
| <String, LinkedEditGroup>{}; | |
| Position exitPosition = null; | |
| CorrectionUtils utils; | |
| AstNode node; | |
| SourceChange change = new SourceChange('<message>'); | |
| AssistProcessor(DartAssistContext dartContext) { | |
| analysisContext = dartContext.analysisContext; | |
| // source | |
| source = dartContext.source; | |
| file = dartContext.source.fullName; | |
| fileStamp = analysisContext.getModificationStamp(source); | |
| // unit | |
| unit = dartContext.unit; | |
| unitElement = dartContext.unit.element; | |
| // library | |
| unitLibraryElement = dartContext.unit.element.library; | |
| unitLibraryFile = unitLibraryElement.source.fullName; | |
| unitLibraryFolder = dirname(unitLibraryFile); | |
| // selection | |
| selectionOffset = dartContext.selectionOffset; | |
| selectionLength = dartContext.selectionLength; | |
| selectionEnd = selectionOffset + selectionLength; | |
| } | |
| /** | |
| * Returns the EOL to use for this [CompilationUnit]. | |
| */ | |
| String get eol => utils.endOfLine; | |
| Future<List<Assist>> compute() async { | |
| utils = new CorrectionUtils(unit); | |
| node = new NodeLocator(selectionOffset, selectionEnd).searchWithin(unit); | |
| if (node == null) { | |
| return assists; | |
| } | |
| // try to add proposals | |
| _addProposal_addTypeAnnotation_DeclaredIdentifier(); | |
| _addProposal_addTypeAnnotation_SimpleFormalParameter(); | |
| _addProposal_addTypeAnnotation_VariableDeclaration(); | |
| _addProposal_assignToLocalVariable(); | |
| _addProposal_convertIntoFinalField(); | |
| _addProposal_convertIntoGetter(); | |
| _addProposal_convertDocumentationIntoBlock(); | |
| _addProposal_convertDocumentationIntoLine(); | |
| _addProposal_convertToBlockFunctionBody(); | |
| _addProposal_convertToExpressionFunctionBody(); | |
| _addProposal_convertToForIndexLoop(); | |
| _addProposal_convertToIsNot_onIs(); | |
| _addProposal_convertToIsNot_onNot(); | |
| _addProposal_convertToIsNotEmpty(); | |
| _addProposal_convertToFieldParameter(); | |
| _addProposal_convertToNormalParameter(); | |
| _addProposal_encapsulateField(); | |
| _addProposal_exchangeOperands(); | |
| _addProposal_importAddShow(); | |
| _addProposal_introduceLocalTestedType(); | |
| _addProposal_invertIf(); | |
| _addProposal_joinIfStatementInner(); | |
| _addProposal_joinIfStatementOuter(); | |
| _addProposal_joinVariableDeclaration_onAssignment(); | |
| _addProposal_joinVariableDeclaration_onDeclaration(); | |
| _addProposal_removeTypeAnnotation(); | |
| _addProposal_replaceConditionalWithIfElse(); | |
| _addProposal_replaceIfElseWithConditional(); | |
| _addProposal_splitAndCondition(); | |
| _addProposal_splitVariableDeclaration(); | |
| _addProposal_surroundWith(); | |
| // done | |
| return assists; | |
| } | |
| FunctionBody getEnclosingFunctionBody() { | |
| { | |
| FunctionExpression function = | |
| node.getAncestor((node) => node is FunctionExpression); | |
| if (function != null) { | |
| return function.body; | |
| } | |
| } | |
| { | |
| FunctionDeclaration function = | |
| node.getAncestor((node) => node is FunctionDeclaration); | |
| if (function != null) { | |
| return function.functionExpression.body; | |
| } | |
| } | |
| { | |
| ConstructorDeclaration constructor = | |
| node.getAncestor((node) => node is ConstructorDeclaration); | |
| if (constructor != null) { | |
| return constructor.body; | |
| } | |
| } | |
| { | |
| MethodDeclaration method = | |
| node.getAncestor((node) => node is MethodDeclaration); | |
| if (method != null) { | |
| return method.body; | |
| } | |
| } | |
| return null; | |
| } | |
| void _addAssist(AssistKind kind, List args, {String assistFile}) { | |
| if (assistFile == null) { | |
| assistFile = file; | |
| } | |
| // check is there are any edits | |
| if (change.edits.isEmpty) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare Change | |
| change.message = formatList(kind.message, args); | |
| linkedPositionGroups.values | |
| .forEach((group) => change.addLinkedEditGroup(group)); | |
| change.selection = exitPosition; | |
| // add Assist | |
| Assist assist = new Assist(kind, change); | |
| assists.add(assist); | |
| // clear | |
| change = new SourceChange('<message>'); | |
| linkedPositionGroups.clear(); | |
| exitPosition = null; | |
| } | |
| void _addIndentEdit(SourceRange range, String oldIndent, String newIndent) { | |
| SourceEdit edit = utils.createIndentEdit(range, oldIndent, newIndent); | |
| doSourceChange_addElementEdit(change, unitElement, edit); | |
| } | |
| /** | |
| * Adds a new [Edit] to [edits]. | |
| */ | |
| void _addInsertEdit(int offset, String text) { | |
| SourceEdit edit = new SourceEdit(offset, 0, text); | |
| doSourceChange_addElementEdit(change, unitElement, edit); | |
| } | |
| void _addProposal_addTypeAnnotation_DeclaredIdentifier() { | |
| DeclaredIdentifier declaredIdentifier = | |
| node.getAncestor((n) => n is DeclaredIdentifier); | |
| if (declaredIdentifier == null) { | |
| ForEachStatement forEach = node.getAncestor((n) => n is ForEachStatement); | |
| int offset = node.offset; | |
| if (forEach != null && | |
| forEach.iterable != null && | |
| offset < forEach.iterable.offset) { | |
| declaredIdentifier = forEach.loopVariable; | |
| } | |
| } | |
| if (declaredIdentifier == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // may be has type annotation already | |
| if (declaredIdentifier.type != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare type source | |
| String typeSource; | |
| DartType type = declaredIdentifier.identifier.bestType; | |
| if (type is InterfaceType || type is FunctionType) { | |
| _configureTargetLocation(node); | |
| Set<LibraryElement> librariesToImport = new Set<LibraryElement>(); | |
| typeSource = utils.getTypeSource(type, librariesToImport); | |
| addLibraryImports(change, unitLibraryElement, librariesToImport); | |
| } else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // type source might be null, if the type is private | |
| if (typeSource == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edit | |
| Token keyword = declaredIdentifier.keyword; | |
| if (keyword.keyword == Keyword.VAR) { | |
| SourceRange range = rangeToken(keyword); | |
| _addReplaceEdit(range, typeSource); | |
| } else { | |
| _addInsertEdit(declaredIdentifier.identifier.offset, '$typeSource '); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.ADD_TYPE_ANNOTATION, []); | |
| } | |
| void _addProposal_addTypeAnnotation_SimpleFormalParameter() { | |
| AstNode node = this.node; | |
| // should be the name of a simple parameter | |
| if (node is! SimpleIdentifier || node.parent is! SimpleFormalParameter) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| SimpleIdentifier name = node; | |
| SimpleFormalParameter parameter = node.parent; | |
| // the parameter should not have a type | |
| if (parameter.type != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare propagated type | |
| DartType type = name.propagatedType; | |
| // TODO(scheglov) If the parameter is in a method declaration, and if the | |
| // method overrides a method that has a type for the corresponding | |
| // parameter, it would be nice to copy down the type from the overridden | |
| // method. | |
| if (type is! InterfaceType) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare type source | |
| String typeSource; | |
| { | |
| _configureTargetLocation(node); | |
| Set<LibraryElement> librariesToImport = new Set<LibraryElement>(); | |
| typeSource = utils.getTypeSource(type, librariesToImport); | |
| addLibraryImports(change, unitLibraryElement, librariesToImport); | |
| } | |
| // type source might be null, if the type is private | |
| if (typeSource == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edit | |
| _addInsertEdit(name.offset, '$typeSource '); | |
| // add proposal | |
| _addAssist(DartAssistKind.ADD_TYPE_ANNOTATION, []); | |
| } | |
| void _addProposal_addTypeAnnotation_VariableDeclaration() { | |
| AstNode node = this.node; | |
| // prepare VariableDeclarationList | |
| VariableDeclarationList declarationList = | |
| node.getAncestor((node) => node is VariableDeclarationList); | |
| if (declarationList == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // may be has type annotation already | |
| if (declarationList.type != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare single VariableDeclaration | |
| List<VariableDeclaration> variables = declarationList.variables; | |
| if (variables.length != 1) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| VariableDeclaration variable = variables[0]; | |
| // must be not after the name of the variable | |
| if (selectionOffset > variable.name.end) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // we need an initializer to get the type from | |
| Expression initializer = variable.initializer; | |
| if (initializer == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| DartType type = initializer.bestType; | |
| // prepare type source | |
| String typeSource; | |
| if (type is InterfaceType || type is FunctionType) { | |
| _configureTargetLocation(node); | |
| Set<LibraryElement> librariesToImport = new Set<LibraryElement>(); | |
| typeSource = utils.getTypeSource(type, librariesToImport); | |
| addLibraryImports(change, unitLibraryElement, librariesToImport); | |
| } else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // type source might be null, if the type is private | |
| if (typeSource == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edit | |
| Token keyword = declarationList.keyword; | |
| if (keyword?.keyword == Keyword.VAR) { | |
| SourceRange range = rangeToken(keyword); | |
| _addReplaceEdit(range, typeSource); | |
| } else { | |
| _addInsertEdit(variable.offset, '$typeSource '); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.ADD_TYPE_ANNOTATION, []); | |
| } | |
| void _addProposal_assignToLocalVariable() { | |
| // prepare enclosing ExpressionStatement | |
| ExpressionStatement expressionStatement; | |
| for (AstNode node = this.node; node != null; node = node.parent) { | |
| if (node is ExpressionStatement) { | |
| expressionStatement = node; | |
| break; | |
| } | |
| if (node is ArgumentList || | |
| node is AssignmentExpression || | |
| node is Statement || | |
| node is ThrowExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| } | |
| if (expressionStatement == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare expression | |
| Expression expression = expressionStatement.expression; | |
| int offset = expression.offset; | |
| // prepare expression type | |
| DartType type = expression.bestType; | |
| if (type.isVoid) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare source | |
| SourceBuilder builder = new SourceBuilder(file, offset); | |
| builder.append('var '); | |
| // prepare excluded names | |
| Set<String> excluded = new Set<String>(); | |
| { | |
| ScopedNameFinder scopedNameFinder = new ScopedNameFinder(offset); | |
| expression.accept(scopedNameFinder); | |
| excluded.addAll(scopedNameFinder.locals.keys.toSet()); | |
| } | |
| // name(s) | |
| { | |
| List<String> suggestions = | |
| getVariableNameSuggestionsForExpression(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.addSuggestion(LinkedEditSuggestionKind.VARIABLE, name); | |
| } | |
| builder.endPosition(); | |
| } | |
| builder.append(' = '); | |
| // add proposal | |
| _insertBuilder(builder); | |
| _addAssist(DartAssistKind.ASSIGN_TO_LOCAL_VARIABLE, []); | |
| } | |
| void _addProposal_convertDocumentationIntoBlock() { | |
| Comment comment = node.getAncestor((n) => n is Comment); | |
| if (comment != null && comment.isDocumentation) { | |
| String prefix = utils.getNodePrefix(comment); | |
| SourceBuilder sb = new SourceBuilder(file, comment.offset); | |
| sb.append('/**'); | |
| sb.append(eol); | |
| for (Token token in comment.tokens) { | |
| if (token is DocumentationCommentToken && | |
| token.type == TokenType.SINGLE_LINE_COMMENT) { | |
| sb.append(prefix); | |
| sb.append(' *'); | |
| sb.append(token.lexeme.substring('///'.length)); | |
| sb.append(eol); | |
| } else { | |
| return; | |
| } | |
| } | |
| sb.append(prefix); | |
| sb.append(' */'); | |
| _insertBuilder(sb, comment.length); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_DOCUMENTATION_INTO_BLOCK, []); | |
| } | |
| void _addProposal_convertDocumentationIntoLine() { | |
| Comment comment = node.getAncestor((n) => n is Comment); | |
| if (comment != null && comment.isDocumentation) { | |
| if (comment.tokens.length == 1) { | |
| Token token = comment.tokens.first; | |
| if (token.type == TokenType.MULTI_LINE_COMMENT) { | |
| String text = token.lexeme; | |
| List<String> lines = text.split('\n'); | |
| String prefix = utils.getNodePrefix(comment); | |
| SourceBuilder sb = new SourceBuilder(file, comment.offset); | |
| bool firstLine = true; | |
| String linePrefix = ''; | |
| for (String line in lines) { | |
| if (firstLine) { | |
| firstLine = false; | |
| String expectedPrefix = '/**'; | |
| if (!line.startsWith(expectedPrefix)) { | |
| return; | |
| } | |
| line = line.substring(expectedPrefix.length).trim(); | |
| if (line.isNotEmpty) { | |
| sb.append('/// '); | |
| sb.append(line); | |
| linePrefix = eol + prefix; | |
| } | |
| } else { | |
| if (line.startsWith(prefix + ' */')) { | |
| break; | |
| } | |
| String expectedPrefix = prefix + ' * '; | |
| if (!line.startsWith(expectedPrefix)) { | |
| return; | |
| } | |
| line = line.substring(expectedPrefix.length).trim(); | |
| sb.append(linePrefix); | |
| sb.append('/// '); | |
| sb.append(line); | |
| linePrefix = eol + prefix; | |
| } | |
| } | |
| _insertBuilder(sb, comment.length); | |
| } | |
| } | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_DOCUMENTATION_INTO_LINE, []); | |
| } | |
| void _addProposal_convertIntoFinalField() { | |
| // Find the enclosing getter. | |
| MethodDeclaration getter; | |
| for (AstNode n = node; n != null; n = n.parent) { | |
| if (n is MethodDeclaration) { | |
| getter = n; | |
| break; | |
| } | |
| if (n is SimpleIdentifier || n is TypeName || n is TypeArgumentList) { | |
| continue; | |
| } | |
| break; | |
| } | |
| if (getter == null || !getter.isGetter) { | |
| return; | |
| } | |
| // Check that there is no corresponding setter. | |
| { | |
| ExecutableElement element = getter.element; | |
| if (element == null) { | |
| return; | |
| } | |
| Element enclosing = element.enclosingElement; | |
| if (enclosing is ClassElement) { | |
| if (enclosing.getSetter(element.name) != null) { | |
| return; | |
| } | |
| } | |
| } | |
| // Try to find the returned expression. | |
| Expression expression; | |
| { | |
| FunctionBody body = getter.body; | |
| if (body is ExpressionFunctionBody) { | |
| expression = body.expression; | |
| } else if (body is BlockFunctionBody) { | |
| List<Statement> statements = body.block.statements; | |
| if (statements.length == 1) { | |
| Statement statement = statements.first; | |
| if (statement is ReturnStatement) { | |
| expression = statement.expression; | |
| } | |
| } | |
| } | |
| } | |
| // Use the returned expression as the field initializer. | |
| if (expression != null) { | |
| AstNode beginNodeToReplace = getter.name; | |
| String code = 'final'; | |
| if (getter.returnType != null) { | |
| beginNodeToReplace = getter.returnType; | |
| code += ' ' + _getNodeText(getter.returnType); | |
| } | |
| code += ' ' + _getNodeText(getter.name); | |
| if (expression is! NullLiteral) { | |
| code += ' = ' + _getNodeText(expression); | |
| } | |
| code += ';'; | |
| _addReplaceEdit(rangeStartEnd(beginNodeToReplace, getter), code); | |
| _addAssist(DartAssistKind.CONVERT_INTO_FINAL_FIELD, []); | |
| } | |
| } | |
| void _addProposal_convertIntoGetter() { | |
| // Find the enclosing field declaration. | |
| FieldDeclaration fieldDeclaration; | |
| for (AstNode n = node; n != null; n = n.parent) { | |
| if (n is FieldDeclaration) { | |
| fieldDeclaration = n; | |
| break; | |
| } | |
| if (n is SimpleIdentifier || | |
| n is VariableDeclaration || | |
| n is VariableDeclarationList || | |
| n is TypeName || | |
| n is TypeArgumentList) { | |
| continue; | |
| } | |
| break; | |
| } | |
| if (fieldDeclaration == null) { | |
| return; | |
| } | |
| // The field must be final and has only one variable. | |
| VariableDeclarationList fieldList = fieldDeclaration.fields; | |
| if (!fieldList.isFinal || fieldList.variables.length != 1) { | |
| return; | |
| } | |
| VariableDeclaration field = fieldList.variables.first; | |
| // Prepare the initializer. | |
| Expression initializer = field.initializer; | |
| if (initializer == null) { | |
| return; | |
| } | |
| // Add proposal. | |
| String code = ''; | |
| if (fieldList.type != null) { | |
| code += _getNodeText(fieldList.type) + ' '; | |
| } | |
| code += 'get'; | |
| code += ' ' + _getNodeText(field.name); | |
| code += ' => ' + _getNodeText(initializer); | |
| code += ';'; | |
| _addReplaceEdit(rangeStartEnd(fieldList.keyword, fieldDeclaration), code); | |
| _addAssist(DartAssistKind.CONVERT_INTO_GETTER, []); | |
| } | |
| void _addProposal_convertToBlockFunctionBody() { | |
| FunctionBody body = getEnclosingFunctionBody(); | |
| // prepare expression body | |
| if (body is! ExpressionFunctionBody || body.isGenerator) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| Expression returnValue = (body as ExpressionFunctionBody).expression; | |
| DartType returnValueType = returnValue.staticType; | |
| String returnValueCode = _getNodeText(returnValue); | |
| // prepare prefix | |
| String prefix = utils.getNodePrefix(body.parent); | |
| String indent = utils.getIndent(1); | |
| // add change | |
| SourceBuilder sb = new SourceBuilder(file, body.offset); | |
| if (body.isAsynchronous) { | |
| sb.append('async '); | |
| } | |
| sb.append('{$eol$prefix$indent'); | |
| if (!returnValueType.isVoid) { | |
| sb.append('return '); | |
| } | |
| sb.append(returnValueCode); | |
| sb.append(';'); | |
| sb.setExitOffset(); | |
| sb.append('$eol$prefix}'); | |
| _insertBuilder(sb, body.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_INTO_BLOCK_BODY, []); | |
| } | |
| void _addProposal_convertToExpressionFunctionBody() { | |
| // prepare current body | |
| FunctionBody body = getEnclosingFunctionBody(); | |
| if (body is! BlockFunctionBody || body.isGenerator) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare return statement | |
| List<Statement> statements = (body as BlockFunctionBody).block.statements; | |
| if (statements.length != 1) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| Statement onlyStatement = statements.first; | |
| // prepare returned expression | |
| Expression returnExpression; | |
| if (onlyStatement is ReturnStatement) { | |
| returnExpression = onlyStatement.expression; | |
| } else if (onlyStatement is ExpressionStatement) { | |
| returnExpression = onlyStatement.expression; | |
| } | |
| if (returnExpression == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add change | |
| SourceBuilder sb = new SourceBuilder(file, body.offset); | |
| if (body.isAsynchronous) { | |
| sb.append('async '); | |
| } | |
| sb.append('=> '); | |
| sb.append(_getNodeText(returnExpression)); | |
| if (body.parent is! FunctionExpression || | |
| body.parent.parent is FunctionDeclaration) { | |
| sb.append(';'); | |
| } | |
| _insertBuilder(sb, body.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_INTO_EXPRESSION_BODY, []); | |
| } | |
| void _addProposal_convertToFieldParameter() { | |
| if (node == null) { | |
| return; | |
| } | |
| // prepare ConstructorDeclaration | |
| ConstructorDeclaration constructor = | |
| node.getAncestor((node) => node is ConstructorDeclaration); | |
| if (constructor == null) { | |
| return; | |
| } | |
| FormalParameterList parameterList = constructor.parameters; | |
| List<ConstructorInitializer> initializers = constructor.initializers; | |
| // prepare parameter | |
| SimpleFormalParameter parameter; | |
| if (node.parent is SimpleFormalParameter && | |
| node.parent.parent is FormalParameterList && | |
| node.parent.parent.parent is ConstructorDeclaration) { | |
| parameter = node.parent; | |
| } | |
| if (node is SimpleIdentifier && | |
| node.parent is ConstructorFieldInitializer) { | |
| String name = (node as SimpleIdentifier).name; | |
| ConstructorFieldInitializer initializer = node.parent; | |
| if (initializer.expression == node) { | |
| for (FormalParameter formalParameter in parameterList.parameters) { | |
| if (formalParameter is SimpleFormalParameter && | |
| formalParameter.identifier.name == name) { | |
| parameter = formalParameter; | |
| } | |
| } | |
| } | |
| } | |
| // analyze parameter | |
| if (parameter != null) { | |
| String parameterName = parameter.identifier.name; | |
| ParameterElement parameterElement = parameter.element; | |
| // check number of references | |
| { | |
| int numOfReferences = 0; | |
| AstVisitor visitor = | |
| new _SimpleIdentifierRecursiveAstVisitor((SimpleIdentifier node) { | |
| if (node.staticElement == parameterElement) { | |
| numOfReferences++; | |
| } | |
| }); | |
| for (ConstructorInitializer initializer in initializers) { | |
| initializer.accept(visitor); | |
| } | |
| if (numOfReferences != 1) { | |
| return; | |
| } | |
| } | |
| // find the field initializer | |
| ConstructorFieldInitializer parameterInitializer; | |
| for (ConstructorInitializer initializer in initializers) { | |
| if (initializer is ConstructorFieldInitializer) { | |
| Expression expression = initializer.expression; | |
| if (expression is SimpleIdentifier && | |
| expression.name == parameterName) { | |
| parameterInitializer = initializer; | |
| } | |
| } | |
| } | |
| if (parameterInitializer == null) { | |
| return; | |
| } | |
| String fieldName = parameterInitializer.fieldName.name; | |
| // replace parameter | |
| _addReplaceEdit(rangeNode(parameter), 'this.$fieldName'); | |
| // remove initializer | |
| int initializerIndex = initializers.indexOf(parameterInitializer); | |
| if (initializers.length == 1) { | |
| _addRemoveEdit(rangeEndEnd(parameterList, parameterInitializer)); | |
| } else { | |
| if (initializerIndex == 0) { | |
| ConstructorInitializer next = initializers[initializerIndex + 1]; | |
| _addRemoveEdit(rangeStartStart(parameterInitializer, next)); | |
| } else { | |
| ConstructorInitializer prev = initializers[initializerIndex - 1]; | |
| _addRemoveEdit(rangeEndEnd(prev, parameterInitializer)); | |
| } | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_TO_FIELD_PARAMETER, []); | |
| } | |
| } | |
| void _addProposal_convertToForIndexLoop() { | |
| // find enclosing ForEachStatement | |
| ForEachStatement forEachStatement = | |
| node.getAncestor((n) => n is ForEachStatement); | |
| if (forEachStatement == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| if (selectionOffset < forEachStatement.offset || | |
| forEachStatement.rightParenthesis.end < selectionOffset) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // loop should declare variable | |
| DeclaredIdentifier loopVariable = forEachStatement.loopVariable; | |
| if (loopVariable == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // iterable should be VariableElement | |
| String listName; | |
| Expression iterable = forEachStatement.iterable; | |
| if (iterable is SimpleIdentifier && | |
| iterable.staticElement is VariableElement) { | |
| listName = iterable.name; | |
| } else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // iterable should be List | |
| { | |
| DartType iterableType = iterable.bestType; | |
| InterfaceType listType = analysisContext.typeProvider.listType; | |
| if (iterableType is! InterfaceType || | |
| iterableType.element != listType.element) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| } | |
| // body should be Block | |
| if (forEachStatement.body is! Block) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| Block body = forEachStatement.body; | |
| // prepare a name for the index variable | |
| String indexName; | |
| { | |
| Set<String> conflicts = | |
| utils.findPossibleLocalVariableConflicts(forEachStatement.offset); | |
| if (!conflicts.contains('i')) { | |
| indexName = 'i'; | |
| } else if (!conflicts.contains('j')) { | |
| indexName = 'j'; | |
| } else if (!conflicts.contains('k')) { | |
| indexName = 'k'; | |
| } else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| } | |
| // prepare environment | |
| String prefix = utils.getNodePrefix(forEachStatement); | |
| String indent = utils.getIndent(1); | |
| int firstBlockLine = utils.getLineContentEnd(body.leftBracket.end); | |
| // add change | |
| _addReplaceEdit( | |
| rangeStartEnd(forEachStatement, forEachStatement.rightParenthesis), | |
| 'for (int $indexName = 0; $indexName < $listName.length; $indexName++)'); | |
| _addInsertEdit(firstBlockLine, | |
| '$prefix$indent$loopVariable = $listName[$indexName];$eol'); | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_INTO_FOR_INDEX, []); | |
| } | |
| void _addProposal_convertToIsNot_onIs() { | |
| // may be child of "is" | |
| AstNode node = this.node; | |
| while (node != null && node is! IsExpression) { | |
| node = node.parent; | |
| } | |
| // prepare "is" | |
| if (node is! IsExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IsExpression isExpression = node as IsExpression; | |
| if (isExpression.notOperator != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare enclosing () | |
| AstNode parent = isExpression.parent; | |
| if (parent is! ParenthesizedExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| ParenthesizedExpression parExpression = parent as ParenthesizedExpression; | |
| // prepare enclosing !() | |
| AstNode parent2 = parent.parent; | |
| if (parent2 is! PrefixExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| PrefixExpression prefExpression = parent2 as PrefixExpression; | |
| if (prefExpression.operator.type != TokenType.BANG) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // strip !() | |
| if (getExpressionParentPrecedence(prefExpression) >= | |
| TokenType.IS.precedence) { | |
| _addRemoveEdit(rangeToken(prefExpression.operator)); | |
| } else { | |
| _addRemoveEdit( | |
| rangeStartEnd(prefExpression, parExpression.leftParenthesis)); | |
| _addRemoveEdit( | |
| rangeStartEnd(parExpression.rightParenthesis, prefExpression)); | |
| } | |
| _addInsertEdit(isExpression.isOperator.end, '!'); | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_INTO_IS_NOT, []); | |
| } | |
| void _addProposal_convertToIsNot_onNot() { | |
| // may be () in prefix expression | |
| if (node is ParenthesizedExpression && node.parent is PrefixExpression) { | |
| node = node.parent; | |
| } | |
| // prepare !() | |
| if (node is! PrefixExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| PrefixExpression prefExpression = node as PrefixExpression; | |
| // should be ! operator | |
| if (prefExpression.operator.type != TokenType.BANG) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare !() | |
| Expression operand = prefExpression.operand; | |
| if (operand is! ParenthesizedExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| ParenthesizedExpression parExpression = operand as ParenthesizedExpression; | |
| operand = parExpression.expression; | |
| // prepare "is" | |
| if (operand is! IsExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IsExpression isExpression = operand as IsExpression; | |
| if (isExpression.notOperator != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // strip !() | |
| if (getExpressionParentPrecedence(prefExpression) >= | |
| TokenType.IS.precedence) { | |
| _addRemoveEdit(rangeToken(prefExpression.operator)); | |
| } else { | |
| _addRemoveEdit( | |
| rangeStartEnd(prefExpression, parExpression.leftParenthesis)); | |
| _addRemoveEdit( | |
| rangeStartEnd(parExpression.rightParenthesis, prefExpression)); | |
| } | |
| _addInsertEdit(isExpression.isOperator.end, '!'); | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_INTO_IS_NOT, []); | |
| } | |
| /** | |
| * Converts "!isEmpty" -> "isNotEmpty" if possible. | |
| */ | |
| void _addProposal_convertToIsNotEmpty() { | |
| // prepare "expr.isEmpty" | |
| AstNode isEmptyAccess = null; | |
| SimpleIdentifier isEmptyIdentifier = null; | |
| if (node is SimpleIdentifier) { | |
| SimpleIdentifier identifier = node as SimpleIdentifier; | |
| AstNode parent = identifier.parent; | |
| // normal case (but rare) | |
| if (parent is PropertyAccess) { | |
| isEmptyIdentifier = parent.propertyName; | |
| isEmptyAccess = parent; | |
| } | |
| // usual case | |
| if (parent is PrefixedIdentifier) { | |
| isEmptyIdentifier = parent.identifier; | |
| isEmptyAccess = parent; | |
| } | |
| } | |
| if (isEmptyIdentifier == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // should be "isEmpty" | |
| Element propertyElement = isEmptyIdentifier.bestElement; | |
| if (propertyElement == null || 'isEmpty' != propertyElement.name) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // should have "isNotEmpty" | |
| Element propertyTarget = propertyElement.enclosingElement; | |
| if (propertyTarget == null || | |
| getChildren(propertyTarget, 'isNotEmpty').isEmpty) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // should be in PrefixExpression | |
| if (isEmptyAccess.parent is! PrefixExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| PrefixExpression prefixExpression = | |
| isEmptyAccess.parent as PrefixExpression; | |
| // should be ! | |
| if (prefixExpression.operator.type != TokenType.BANG) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // do replace | |
| _addRemoveEdit(rangeStartStart(prefixExpression, prefixExpression.operand)); | |
| _addReplaceEdit(rangeNode(isEmptyIdentifier), 'isNotEmpty'); | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_INTO_IS_NOT_EMPTY, []); | |
| } | |
| void _addProposal_convertToNormalParameter() { | |
| if (node is SimpleIdentifier && | |
| node.parent is FieldFormalParameter && | |
| node.parent.parent is FormalParameterList && | |
| node.parent.parent.parent is ConstructorDeclaration) { | |
| ConstructorDeclaration constructor = node.parent.parent.parent; | |
| FormalParameterList parameterList = node.parent.parent; | |
| FieldFormalParameter parameter = node.parent; | |
| ParameterElement parameterElement = parameter.element; | |
| String name = (node as SimpleIdentifier).name; | |
| // prepare type | |
| DartType type = parameterElement.type; | |
| Set<LibraryElement> librariesToImport = new Set<LibraryElement>(); | |
| String typeCode = utils.getTypeSource(type, librariesToImport); | |
| // replace parameter | |
| if (type.isDynamic) { | |
| _addReplaceEdit(rangeNode(parameter), name); | |
| } else { | |
| _addReplaceEdit(rangeNode(parameter), '$typeCode $name'); | |
| } | |
| // add field initializer | |
| List<ConstructorInitializer> initializers = constructor.initializers; | |
| if (initializers.isEmpty) { | |
| _addInsertEdit(parameterList.end, ' : $name = $name'); | |
| } else { | |
| _addInsertEdit(initializers.last.end, ', $name = $name'); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.CONVERT_TO_NORMAL_PARAMETER, []); | |
| } | |
| } | |
| void _addProposal_encapsulateField() { | |
| // find FieldDeclaration | |
| FieldDeclaration fieldDeclaration = | |
| node.getAncestor((x) => x is FieldDeclaration); | |
| if (fieldDeclaration == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // not interesting for static | |
| if (fieldDeclaration.isStatic) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // has a parse error | |
| VariableDeclarationList variableList = fieldDeclaration.fields; | |
| if (variableList.keyword == null && variableList.type == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // not interesting for final | |
| if (variableList.isFinal) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // should have exactly one field | |
| List<VariableDeclaration> fields = variableList.variables; | |
| if (fields.length != 1) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| VariableDeclaration field = fields.first; | |
| SimpleIdentifier nameNode = field.name; | |
| FieldElement fieldElement = nameNode.staticElement; | |
| // should have a public name | |
| String name = nameNode.name; | |
| if (Identifier.isPrivateName(name)) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // should be on the name | |
| if (nameNode != node) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // rename field | |
| _addReplaceEdit(rangeNode(nameNode), '_$name'); | |
| // update references in constructors | |
| ClassDeclaration classDeclaration = fieldDeclaration.parent; | |
| for (ClassMember member in classDeclaration.members) { | |
| if (member is ConstructorDeclaration) { | |
| for (FormalParameter parameter in member.parameters.parameters) { | |
| ParameterElement parameterElement = parameter.element; | |
| if (parameterElement is FieldFormalParameterElement && | |
| parameterElement.field == fieldElement) { | |
| _addReplaceEdit(rangeNode(parameter.identifier), '_$name'); | |
| } | |
| } | |
| } | |
| } | |
| // add accessors | |
| String eol2 = eol + eol; | |
| String typeNameCode = | |
| variableList.type != null ? _getNodeText(variableList.type) + ' ' : ''; | |
| String getterCode = '$eol2 ${typeNameCode}get $name => _$name;'; | |
| String setterCode = '$eol2' | |
| ' void set $name($typeNameCode$name) {$eol' | |
| ' _$name = $name;$eol' | |
| ' }'; | |
| _addInsertEdit(fieldDeclaration.end, getterCode + setterCode); | |
| // add proposal | |
| _addAssist(DartAssistKind.ENCAPSULATE_FIELD, []); | |
| } | |
| void _addProposal_exchangeOperands() { | |
| // check that user invokes quick assist on binary expression | |
| if (node is! BinaryExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| BinaryExpression binaryExpression = node as BinaryExpression; | |
| // prepare operator position | |
| if (!_isOperatorSelected( | |
| binaryExpression, selectionOffset, selectionLength)) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edits | |
| { | |
| Expression leftOperand = binaryExpression.leftOperand; | |
| Expression rightOperand = binaryExpression.rightOperand; | |
| // find "wide" enclosing binary expression with same operator | |
| while (binaryExpression.parent is BinaryExpression) { | |
| BinaryExpression newBinaryExpression = | |
| binaryExpression.parent as BinaryExpression; | |
| if (newBinaryExpression.operator.type != | |
| binaryExpression.operator.type) { | |
| _coverageMarker(); | |
| break; | |
| } | |
| binaryExpression = newBinaryExpression; | |
| } | |
| // exchange parts of "wide" expression parts | |
| SourceRange leftRange = rangeStartEnd(binaryExpression, leftOperand); | |
| SourceRange rightRange = rangeStartEnd(rightOperand, binaryExpression); | |
| _addReplaceEdit(leftRange, _getRangeText(rightRange)); | |
| _addReplaceEdit(rightRange, _getRangeText(leftRange)); | |
| // maybe replace the operator | |
| { | |
| Token operator = binaryExpression.operator; | |
| // prepare a new operator | |
| String newOperator = null; | |
| TokenType operatorType = operator.type; | |
| if (operatorType == TokenType.LT) { | |
| newOperator = '>'; | |
| } else if (operatorType == TokenType.LT_EQ) { | |
| newOperator = '>='; | |
| } else if (operatorType == TokenType.GT) { | |
| newOperator = '<'; | |
| } else if (operatorType == TokenType.GT_EQ) { | |
| newOperator = '<='; | |
| } | |
| // replace the operator | |
| if (newOperator != null) { | |
| _addReplaceEdit(rangeToken(operator), newOperator); | |
| } | |
| } | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.EXCHANGE_OPERANDS, []); | |
| } | |
| void _addProposal_importAddShow() { | |
| // prepare ImportDirective | |
| ImportDirective importDirective = | |
| node.getAncestor((node) => node is ImportDirective); | |
| if (importDirective == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // there should be no existing combinators | |
| if (importDirective.combinators.isNotEmpty) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare whole import namespace | |
| ImportElement importElement = importDirective.element; | |
| if (importElement == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| Map<String, Element> namespace = getImportNamespace(importElement); | |
| // prepare names of referenced elements (from this import) | |
| SplayTreeSet<String> referencedNames = new SplayTreeSet<String>(); | |
| _SimpleIdentifierRecursiveAstVisitor visitor = | |
| new _SimpleIdentifierRecursiveAstVisitor((SimpleIdentifier node) { | |
| Element element = node.staticElement; | |
| if (element != null && namespace[node.name] == element) { | |
| referencedNames.add(element.displayName); | |
| } | |
| }); | |
| unit.accept(visitor); | |
| // ignore if unused | |
| if (referencedNames.isEmpty) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare change | |
| String showCombinator = ' show ${referencedNames.join(', ')}'; | |
| _addInsertEdit(importDirective.end - 1, showCombinator); | |
| // add proposal | |
| _addAssist(DartAssistKind.IMPORT_ADD_SHOW, []); | |
| } | |
| void _addProposal_introduceLocalTestedType() { | |
| AstNode node = this.node; | |
| if (node is IfStatement) { | |
| node = (node as IfStatement).condition; | |
| } else if (node is WhileStatement) { | |
| node = (node as WhileStatement).condition; | |
| } | |
| // prepare IsExpression | |
| if (node is! IsExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IsExpression isExpression = node; | |
| DartType castType = isExpression.type.type; | |
| String castTypeCode = _getNodeText(isExpression.type); | |
| // prepare environment | |
| String indent = utils.getIndent(1); | |
| String prefix; | |
| Block targetBlock; | |
| { | |
| Statement statement = node.getAncestor((n) => n is Statement); | |
| if (statement is IfStatement && statement.thenStatement is Block) { | |
| targetBlock = statement.thenStatement; | |
| } else if (statement is WhileStatement && statement.body is Block) { | |
| targetBlock = statement.body; | |
| } else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| prefix = utils.getNodePrefix(statement); | |
| } | |
| // prepare location | |
| int offset; | |
| String statementPrefix; | |
| if (isExpression.notOperator == null) { | |
| offset = targetBlock.leftBracket.end; | |
| statementPrefix = indent; | |
| } else { | |
| offset = targetBlock.rightBracket.end; | |
| statementPrefix = ''; | |
| } | |
| // prepare source | |
| SourceBuilder builder = new SourceBuilder(file, offset); | |
| builder.append(eol + prefix + statementPrefix); | |
| builder.append(castTypeCode); | |
| // prepare excluded names | |
| Set<String> excluded = new Set<String>(); | |
| { | |
| ScopedNameFinder scopedNameFinder = new ScopedNameFinder(offset); | |
| isExpression.accept(scopedNameFinder); | |
| excluded.addAll(scopedNameFinder.locals.keys.toSet()); | |
| } | |
| // name(s) | |
| { | |
| List<String> suggestions = | |
| getVariableNameSuggestionsForExpression(castType, null, excluded); | |
| builder.append(' '); | |
| builder.startPosition('NAME'); | |
| for (int i = 0; i < suggestions.length; i++) { | |
| String name = suggestions[i]; | |
| if (i == 0) { | |
| builder.append(name); | |
| } | |
| builder.addSuggestion(LinkedEditSuggestionKind.VARIABLE, name); | |
| } | |
| builder.endPosition(); | |
| } | |
| builder.append(' = '); | |
| builder.append(_getNodeText(isExpression.expression)); | |
| builder.append(';'); | |
| builder.setExitOffset(); | |
| // add proposal | |
| _insertBuilder(builder); | |
| _addAssist(DartAssistKind.INTRODUCE_LOCAL_CAST_TYPE, []); | |
| } | |
| void _addProposal_invertIf() { | |
| if (node is! IfStatement) { | |
| return; | |
| } | |
| IfStatement ifStatement = node as IfStatement; | |
| Expression condition = ifStatement.condition; | |
| // should have both "then" and "else" | |
| Statement thenStatement = ifStatement.thenStatement; | |
| Statement elseStatement = ifStatement.elseStatement; | |
| if (thenStatement == null || elseStatement == null) { | |
| return; | |
| } | |
| // prepare source | |
| String invertedCondition = utils.invertCondition(condition); | |
| String thenSource = _getNodeText(thenStatement); | |
| String elseSource = _getNodeText(elseStatement); | |
| // do replacements | |
| _addReplaceEdit(rangeNode(condition), invertedCondition); | |
| _addReplaceEdit(rangeNode(thenStatement), elseSource); | |
| _addReplaceEdit(rangeNode(elseStatement), thenSource); | |
| // add proposal | |
| _addAssist(DartAssistKind.INVERT_IF_STATEMENT, []); | |
| } | |
| void _addProposal_joinIfStatementInner() { | |
| // climb up condition to the (supposedly) "if" statement | |
| AstNode node = this.node; | |
| while (node is Expression) { | |
| node = node.parent; | |
| } | |
| // prepare target "if" statement | |
| if (node is! IfStatement) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IfStatement targetIfStatement = node as IfStatement; | |
| if (targetIfStatement.elseStatement != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare inner "if" statement | |
| Statement targetThenStatement = targetIfStatement.thenStatement; | |
| Statement innerStatement = getSingleStatement(targetThenStatement); | |
| if (innerStatement is! IfStatement) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IfStatement innerIfStatement = innerStatement as IfStatement; | |
| if (innerIfStatement.elseStatement != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare environment | |
| String prefix = utils.getNodePrefix(targetIfStatement); | |
| // merge conditions | |
| String condition; | |
| { | |
| Expression targetCondition = targetIfStatement.condition; | |
| Expression innerCondition = innerIfStatement.condition; | |
| String targetConditionSource = _getNodeText(targetCondition); | |
| String innerConditionSource = _getNodeText(innerCondition); | |
| if (_shouldWrapParenthesisBeforeAnd(targetCondition)) { | |
| targetConditionSource = '($targetConditionSource)'; | |
| } | |
| if (_shouldWrapParenthesisBeforeAnd(innerCondition)) { | |
| innerConditionSource = '($innerConditionSource)'; | |
| } | |
| condition = '$targetConditionSource && $innerConditionSource'; | |
| } | |
| // replace target "if" statement | |
| { | |
| Statement innerThenStatement = innerIfStatement.thenStatement; | |
| List<Statement> innerThenStatements = getStatements(innerThenStatement); | |
| SourceRange lineRanges = | |
| utils.getLinesRangeStatements(innerThenStatements); | |
| String oldSource = utils.getRangeText(lineRanges); | |
| String newSource = utils.indentSourceLeftRight(oldSource, false); | |
| _addReplaceEdit(rangeNode(targetIfStatement), | |
| 'if ($condition) {$eol$newSource$prefix}'); | |
| } | |
| // done | |
| _addAssist(DartAssistKind.JOIN_IF_WITH_INNER, []); | |
| } | |
| void _addProposal_joinIfStatementOuter() { | |
| // climb up condition to the (supposedly) "if" statement | |
| AstNode node = this.node; | |
| while (node is Expression) { | |
| node = node.parent; | |
| } | |
| // prepare target "if" statement | |
| if (node is! IfStatement) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IfStatement targetIfStatement = node as IfStatement; | |
| if (targetIfStatement.elseStatement != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare outer "if" statement | |
| AstNode parent = targetIfStatement.parent; | |
| if (parent is Block) { | |
| if ((parent as Block).statements.length != 1) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| parent = parent.parent; | |
| } | |
| if (parent is! IfStatement) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IfStatement outerIfStatement = parent as IfStatement; | |
| if (outerIfStatement.elseStatement != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare environment | |
| String prefix = utils.getNodePrefix(outerIfStatement); | |
| // merge conditions | |
| String condition; | |
| { | |
| Expression targetCondition = targetIfStatement.condition; | |
| Expression outerCondition = outerIfStatement.condition; | |
| String targetConditionSource = _getNodeText(targetCondition); | |
| String outerConditionSource = _getNodeText(outerCondition); | |
| if (_shouldWrapParenthesisBeforeAnd(targetCondition)) { | |
| targetConditionSource = '($targetConditionSource)'; | |
| } | |
| if (_shouldWrapParenthesisBeforeAnd(outerCondition)) { | |
| outerConditionSource = '($outerConditionSource)'; | |
| } | |
| condition = '$outerConditionSource && $targetConditionSource'; | |
| } | |
| // replace outer "if" statement | |
| { | |
| Statement targetThenStatement = targetIfStatement.thenStatement; | |
| List<Statement> targetThenStatements = getStatements(targetThenStatement); | |
| SourceRange lineRanges = | |
| utils.getLinesRangeStatements(targetThenStatements); | |
| String oldSource = utils.getRangeText(lineRanges); | |
| String newSource = utils.indentSourceLeftRight(oldSource, false); | |
| _addReplaceEdit(rangeNode(outerIfStatement), | |
| 'if ($condition) {$eol$newSource$prefix}'); | |
| } | |
| // done | |
| _addAssist(DartAssistKind.JOIN_IF_WITH_OUTER, []); | |
| } | |
| void _addProposal_joinVariableDeclaration_onAssignment() { | |
| // check that node is LHS in assignment | |
| if (node is SimpleIdentifier && | |
| node.parent is AssignmentExpression && | |
| (node.parent as AssignmentExpression).leftHandSide == node && | |
| node.parent.parent is ExpressionStatement) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| AssignmentExpression assignExpression = node.parent as AssignmentExpression; | |
| // check that binary expression is assignment | |
| if (assignExpression.operator.type != TokenType.EQ) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare "declaration" statement | |
| Element element = (node as SimpleIdentifier).staticElement; | |
| if (element == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| int declOffset = element.nameOffset; | |
| AstNode declNode = new NodeLocator(declOffset).searchWithin(unit); | |
| if (declNode != null && | |
| declNode.parent is VariableDeclaration && | |
| (declNode.parent as VariableDeclaration).name == declNode && | |
| declNode.parent.parent is VariableDeclarationList && | |
| declNode.parent.parent.parent is VariableDeclarationStatement) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| VariableDeclaration decl = declNode.parent as VariableDeclaration; | |
| VariableDeclarationStatement declStatement = | |
| decl.parent.parent as VariableDeclarationStatement; | |
| // may be has initializer | |
| if (decl.initializer != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // check that "declaration" statement declared only one variable | |
| if (declStatement.variables.variables.length != 1) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // check that the "declaration" and "assignment" statements are | |
| // parts of the same Block | |
| ExpressionStatement assignStatement = | |
| node.parent.parent as ExpressionStatement; | |
| if (assignStatement.parent is Block && | |
| assignStatement.parent == declStatement.parent) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| Block block = assignStatement.parent as Block; | |
| // check that "declaration" and "assignment" statements are adjacent | |
| List<Statement> statements = block.statements; | |
| if (statements.indexOf(assignStatement) == | |
| statements.indexOf(declStatement) + 1) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edits | |
| { | |
| int assignOffset = assignExpression.operator.offset; | |
| _addReplaceEdit(rangeEndStart(declNode, assignOffset), ' '); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.JOIN_VARIABLE_DECLARATION, []); | |
| } | |
| void _addProposal_joinVariableDeclaration_onDeclaration() { | |
| // prepare enclosing VariableDeclarationList | |
| VariableDeclarationList declList = | |
| node.getAncestor((node) => node is VariableDeclarationList); | |
| if (declList != null && declList.variables.length == 1) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| VariableDeclaration decl = declList.variables[0]; | |
| // already initialized | |
| if (decl.initializer != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare VariableDeclarationStatement in Block | |
| if (declList.parent is VariableDeclarationStatement && | |
| declList.parent.parent is Block) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| VariableDeclarationStatement declStatement = | |
| declList.parent as VariableDeclarationStatement; | |
| Block block = declStatement.parent as Block; | |
| List<Statement> statements = block.statements; | |
| // prepare assignment | |
| AssignmentExpression assignExpression; | |
| { | |
| // declaration should not be last Statement | |
| int declIndex = statements.indexOf(declStatement); | |
| if (declIndex < statements.length - 1) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // next Statement should be assignment | |
| Statement assignStatement = statements[declIndex + 1]; | |
| if (assignStatement is ExpressionStatement) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| ExpressionStatement expressionStatement = | |
| assignStatement as ExpressionStatement; | |
| // expression should be assignment | |
| if (expressionStatement.expression is AssignmentExpression) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| assignExpression = expressionStatement.expression as AssignmentExpression; | |
| } | |
| // check that pure assignment | |
| if (assignExpression.operator.type != TokenType.EQ) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edits | |
| { | |
| int assignOffset = assignExpression.operator.offset; | |
| _addReplaceEdit(rangeEndStart(decl.name, assignOffset), ' '); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.JOIN_VARIABLE_DECLARATION, []); | |
| } | |
| void _addProposal_removeTypeAnnotation() { | |
| VariableDeclarationList declarationList = | |
| node.getAncestor((n) => n is VariableDeclarationList); | |
| if (declarationList == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // we need a type | |
| TypeName typeNode = declarationList.type; | |
| if (typeNode == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // ignore if an incomplete variable declaration | |
| if (declarationList.variables.length == 1 && | |
| declarationList.variables[0].name.isSynthetic) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // must be not after the name of the variable | |
| VariableDeclaration firstVariable = declarationList.variables[0]; | |
| if (selectionOffset > firstVariable.name.end) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // add edit | |
| Token keyword = declarationList.keyword; | |
| SourceRange typeRange = rangeStartStart(typeNode, firstVariable); | |
| if (keyword != null && keyword.lexeme != 'var') { | |
| _addReplaceEdit(typeRange, ''); | |
| } else { | |
| _addReplaceEdit(typeRange, 'var '); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.REMOVE_TYPE_ANNOTATION, []); | |
| } | |
| void _addProposal_replaceConditionalWithIfElse() { | |
| ConditionalExpression conditional = null; | |
| // may be on Statement with Conditional | |
| Statement statement = node.getAncestor((node) => node is Statement); | |
| if (statement == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // variable declaration | |
| bool inVariable = false; | |
| if (statement is VariableDeclarationStatement) { | |
| VariableDeclarationStatement variableStatement = statement; | |
| for (VariableDeclaration variable | |
| in variableStatement.variables.variables) { | |
| if (variable.initializer is ConditionalExpression) { | |
| conditional = variable.initializer as ConditionalExpression; | |
| inVariable = true; | |
| break; | |
| } | |
| } | |
| } | |
| // assignment | |
| bool inAssignment = false; | |
| if (statement is ExpressionStatement) { | |
| ExpressionStatement exprStmt = statement; | |
| if (exprStmt.expression is AssignmentExpression) { | |
| AssignmentExpression assignment = | |
| exprStmt.expression as AssignmentExpression; | |
| if (assignment.operator.type == TokenType.EQ && | |
| assignment.rightHandSide is ConditionalExpression) { | |
| conditional = assignment.rightHandSide as ConditionalExpression; | |
| inAssignment = true; | |
| } | |
| } | |
| } | |
| // return | |
| bool inReturn = false; | |
| if (statement is ReturnStatement) { | |
| ReturnStatement returnStatement = statement; | |
| if (returnStatement.expression is ConditionalExpression) { | |
| conditional = returnStatement.expression as ConditionalExpression; | |
| inReturn = true; | |
| } | |
| } | |
| // prepare environment | |
| String indent = utils.getIndent(1); | |
| String prefix = utils.getNodePrefix(statement); | |
| // Type v = Conditional; | |
| if (inVariable) { | |
| VariableDeclaration variable = conditional.parent as VariableDeclaration; | |
| _addRemoveEdit(rangeEndEnd(variable.name, conditional)); | |
| String conditionSrc = _getNodeText(conditional.condition); | |
| String thenSrc = _getNodeText(conditional.thenExpression); | |
| String elseSrc = _getNodeText(conditional.elseExpression); | |
| String name = variable.name.name; | |
| String src = eol; | |
| src += prefix + 'if ($conditionSrc) {' + eol; | |
| src += prefix + indent + '$name = $thenSrc;' + eol; | |
| src += prefix + '} else {' + eol; | |
| src += prefix + indent + '$name = $elseSrc;' + eol; | |
| src += prefix + '}'; | |
| _addReplaceEdit(rangeEndLength(statement, 0), src); | |
| } | |
| // v = Conditional; | |
| if (inAssignment) { | |
| AssignmentExpression assignment = | |
| conditional.parent as AssignmentExpression; | |
| Expression leftSide = assignment.leftHandSide; | |
| String conditionSrc = _getNodeText(conditional.condition); | |
| String thenSrc = _getNodeText(conditional.thenExpression); | |
| String elseSrc = _getNodeText(conditional.elseExpression); | |
| String name = _getNodeText(leftSide); | |
| String src = ''; | |
| src += 'if ($conditionSrc) {' + eol; | |
| src += prefix + indent + '$name = $thenSrc;' + eol; | |
| src += prefix + '} else {' + eol; | |
| src += prefix + indent + '$name = $elseSrc;' + eol; | |
| src += prefix + '}'; | |
| _addReplaceEdit(rangeNode(statement), src); | |
| } | |
| // return Conditional; | |
| if (inReturn) { | |
| String conditionSrc = _getNodeText(conditional.condition); | |
| String thenSrc = _getNodeText(conditional.thenExpression); | |
| String elseSrc = _getNodeText(conditional.elseExpression); | |
| String src = ''; | |
| src += 'if ($conditionSrc) {' + eol; | |
| src += prefix + indent + 'return $thenSrc;' + eol; | |
| src += prefix + '} else {' + eol; | |
| src += prefix + indent + 'return $elseSrc;' + eol; | |
| src += prefix + '}'; | |
| _addReplaceEdit(rangeNode(statement), src); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.REPLACE_CONDITIONAL_WITH_IF_ELSE, []); | |
| } | |
| void _addProposal_replaceIfElseWithConditional() { | |
| // should be "if" | |
| if (node is! IfStatement) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IfStatement ifStatement = node as IfStatement; | |
| // single then/else statements | |
| Statement thenStatement = getSingleStatement(ifStatement.thenStatement); | |
| Statement elseStatement = getSingleStatement(ifStatement.elseStatement); | |
| if (thenStatement == null || elseStatement == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // returns | |
| if (thenStatement is ReturnStatement && elseStatement is ReturnStatement) { | |
| String conditionSrc = _getNodeText(ifStatement.condition); | |
| String theSrc = _getNodeText(thenStatement.expression); | |
| String elseSrc = _getNodeText(elseStatement.expression); | |
| _addReplaceEdit( | |
| rangeNode(ifStatement), 'return $conditionSrc ? $theSrc : $elseSrc;'); | |
| } | |
| // assignments -> v = Conditional; | |
| if (thenStatement is ExpressionStatement && | |
| elseStatement is ExpressionStatement) { | |
| Expression thenExpression = thenStatement.expression; | |
| Expression elseExpression = elseStatement.expression; | |
| if (thenExpression is AssignmentExpression && | |
| elseExpression is AssignmentExpression) { | |
| AssignmentExpression thenAssignment = thenExpression; | |
| AssignmentExpression elseAssignment = elseExpression; | |
| String thenTarget = _getNodeText(thenAssignment.leftHandSide); | |
| String elseTarget = _getNodeText(elseAssignment.leftHandSide); | |
| if (thenAssignment.operator.type == TokenType.EQ && | |
| elseAssignment.operator.type == TokenType.EQ && | |
| thenTarget == elseTarget) { | |
| String conditionSrc = _getNodeText(ifStatement.condition); | |
| String theSrc = _getNodeText(thenAssignment.rightHandSide); | |
| String elseSrc = _getNodeText(elseAssignment.rightHandSide); | |
| _addReplaceEdit(rangeNode(ifStatement), | |
| '$thenTarget = $conditionSrc ? $theSrc : $elseSrc;'); | |
| } | |
| } | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL, []); | |
| } | |
| void _addProposal_splitAndCondition() { | |
| // check that user invokes quick assist on binary expression | |
| if (node is! BinaryExpression) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| BinaryExpression binaryExpression = node as BinaryExpression; | |
| // prepare operator position | |
| if (!_isOperatorSelected( | |
| binaryExpression, selectionOffset, selectionLength)) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // should be && | |
| if (binaryExpression.operator.type != TokenType.AMPERSAND_AMPERSAND) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare "if" | |
| Statement statement = node.getAncestor((node) => node is Statement); | |
| if (statement is! IfStatement) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| IfStatement ifStatement = statement as IfStatement; | |
| // no support "else" | |
| if (ifStatement.elseStatement != null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // check that binary expression is part of first level && condition of "if" | |
| BinaryExpression condition = binaryExpression; | |
| while (condition.parent is BinaryExpression && | |
| (condition.parent as BinaryExpression).operator.type == | |
| TokenType.AMPERSAND_AMPERSAND) { | |
| condition = condition.parent as BinaryExpression; | |
| } | |
| if (ifStatement.condition != condition) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // prepare environment | |
| String prefix = utils.getNodePrefix(ifStatement); | |
| String indent = utils.getIndent(1); | |
| // prepare "rightCondition" | |
| String rightConditionSource; | |
| { | |
| SourceRange rightConditionRange = | |
| rangeStartEnd(binaryExpression.rightOperand, condition); | |
| rightConditionSource = _getRangeText(rightConditionRange); | |
| } | |
| // remove "&& rightCondition" | |
| _addRemoveEdit(rangeEndEnd(binaryExpression.leftOperand, condition)); | |
| // update "then" statement | |
| Statement thenStatement = ifStatement.thenStatement; | |
| if (thenStatement is Block) { | |
| Block thenBlock = thenStatement; | |
| SourceRange thenBlockRange = rangeNode(thenBlock); | |
| // insert inner "if" with right part of "condition" | |
| { | |
| String source = '$eol$prefix${indent}if ($rightConditionSource) {'; | |
| int thenBlockInsideOffset = thenBlockRange.offset + 1; | |
| _addInsertEdit(thenBlockInsideOffset, source); | |
| } | |
| // insert closing "}" for inner "if" | |
| { | |
| int thenBlockEnd = thenBlockRange.end; | |
| String source = "$indent}"; | |
| // 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.rightParenthesis.offset + 1, source); | |
| } | |
| // indent "then" statements to correspond inner "if" | |
| { | |
| List<Statement> thenStatements = getStatements(thenStatement); | |
| SourceRange linesRange = utils.getLinesRangeStatements(thenStatements); | |
| String thenIndentOld = '$prefix$indent'; | |
| String thenIndentNew = '$thenIndentOld$indent'; | |
| _addIndentEdit(linesRange, thenIndentOld, thenIndentNew); | |
| } | |
| // add proposal | |
| _addAssist(DartAssistKind.SPLIT_AND_CONDITION, []); | |
| } | |
| void _addProposal_splitVariableDeclaration() { | |
| // prepare DartVariableStatement, should be part of Block | |
| VariableDeclarationStatement statement = | |
| node.getAncestor((node) => node is VariableDeclarationStatement); | |
| if (statement != null && statement.parent is Block) {} else { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // check that statement declares single variable | |
| List<VariableDeclaration> variables = statement.variables.variables; | |
| if (variables.length != 1) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| VariableDeclaration variable = variables[0]; | |
| // prepare initializer | |
| Expression initializer = variable.initializer; | |
| if (initializer == null) { | |
| _coverageMarker(); | |
| return; | |
| } | |
| // remove initializer value | |
| _addRemoveEdit(rangeEndStart(variable.name, statement.semicolon)); | |
| // add assignment statement | |
| String indent = utils.getNodePrefix(statement); | |
| String name = variable.name.name; | |
| String initSrc = _getNodeText(initializer); | |
| SourceRange assignRange = rangeEndLength(statement, 0); | |
| _addReplaceEdit(assignRange, eol + indent + name + ' = ' + initSrc + ';'); | |
| // add proposal | |
| _addAssist(DartAssistKind.SPLIT_VARIABLE_DECLARATION, []); | |
| } | |
| void _addProposal_surroundWith() { | |
| // prepare selected statements | |
| List<Statement> selectedStatements; | |
| { | |
| SourceRange selection = | |
| rangeStartLength(selectionOffset, selectionLength); | |
| StatementAnalyzer selectionAnalyzer = | |
| new StatementAnalyzer(unit, selection); | |
| unit.accept(selectionAnalyzer); | |
| List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes; | |
| // convert nodes to statements | |
| selectedStatements = []; | |
| for (AstNode selectedNode in selectedNodes) { | |
| if (selectedNode is Statement) { | |
| selectedStatements.add(selectedNode); | |
| } | |
| } | |
| // we want only statements | |
| if (selectedStatements.isEmpty || | |
| selectedStatements.length != selectedNodes.length) { | |
| return; | |
| } | |
| } | |
| // prepare statement information | |
| Statement firstStatement = selectedStatements[0]; | |
| Statement lastStatement = selectedStatements[selectedStatements.length - 1]; | |
| SourceRange statementsRange = | |
| utils.getLinesRangeStatements(selectedStatements); | |
| // prepare environment | |
| String indentOld = utils.getNodePrefix(firstStatement); | |
| String indentNew = '$indentOld${utils.getIndent(1)}'; | |
| String indentedCode = | |
| utils.replaceSourceRangeIndent(statementsRange, indentOld, indentNew); | |
| // "block" | |
| { | |
| _addInsertEdit(statementsRange.offset, '$indentOld{$eol'); | |
| _addIndentEdit(statementsRange, indentOld, indentNew); | |
| _addInsertEdit(statementsRange.end, '$indentOld}$eol'); | |
| exitPosition = _newPosition(lastStatement.end); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_BLOCK, []); | |
| } | |
| // "if" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, offset); | |
| sb.append(indentOld); | |
| sb.append('if ('); | |
| { | |
| sb.startPosition('CONDITION'); | |
| sb.append('condition'); | |
| sb.endPosition(); | |
| } | |
| sb.append(') {'); | |
| sb.append(eol); | |
| sb.append(indentedCode); | |
| sb.append(indentOld); | |
| sb.append('}'); | |
| exitPosition = _newPosition(sb.offset + sb.length); | |
| sb.append(eol); | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_IF, []); | |
| } | |
| // "while" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, offset); | |
| sb.append(indentOld); | |
| sb.append('while ('); | |
| { | |
| sb.startPosition('CONDITION'); | |
| sb.append('condition'); | |
| sb.endPosition(); | |
| } | |
| sb.append(') {'); | |
| sb.append(eol); | |
| sb.append(indentedCode); | |
| sb.append(indentOld); | |
| sb.append('}'); | |
| exitPosition = _newPosition(sb.offset + sb.length); | |
| sb.append(eol); | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_WHILE, []); | |
| } | |
| // "for-in" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, 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); | |
| sb.append(indentedCode); | |
| sb.append(indentOld); | |
| sb.append('}'); | |
| exitPosition = _newPosition(sb.offset + sb.length); | |
| sb.append(eol); | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_FOR_IN, []); | |
| } | |
| // "for" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, 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); | |
| sb.append(indentedCode); | |
| sb.append(indentOld); | |
| sb.append('}'); | |
| exitPosition = _newPosition(sb.offset + sb.length); | |
| sb.append(eol); | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_FOR, []); | |
| } | |
| // "do-while" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, offset); | |
| sb.append(indentOld); | |
| sb.append('do {'); | |
| sb.append(eol); | |
| sb.append(indentedCode); | |
| sb.append(indentOld); | |
| sb.append('} while ('); | |
| { | |
| sb.startPosition('CONDITION'); | |
| sb.append('condition'); | |
| sb.endPosition(); | |
| } | |
| sb.append(');'); | |
| exitPosition = _newPosition(sb.offset + sb.length); | |
| sb.append(eol); | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_DO_WHILE, []); | |
| } | |
| // "try-catch" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, offset); | |
| sb.append(indentOld); | |
| sb.append('try {'); | |
| sb.append(eol); | |
| sb.append(indentedCode); | |
| 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.setExitOffset(); | |
| } | |
| sb.append(eol); | |
| // | |
| sb.append(indentOld); | |
| sb.append('}'); | |
| sb.append(eol); | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_TRY_CATCH, []); | |
| } | |
| // "try-finally" | |
| { | |
| int offset = statementsRange.offset; | |
| SourceBuilder sb = new SourceBuilder(file, offset); | |
| // | |
| sb.append(indentOld); | |
| sb.append('try {'); | |
| sb.append(eol); | |
| // | |
| sb.append(indentedCode); | |
| // | |
| sb.append(indentOld); | |
| sb.append('} finally {'); | |
| sb.append(eol); | |
| // | |
| sb.append(indentNew); | |
| { | |
| sb.startPosition('FINALLY'); | |
| sb.append('// TODO'); | |
| sb.endPosition(); | |
| sb.setExitOffset(); | |
| } | |
| sb.setExitOffset(); | |
| sb.append(eol); | |
| // | |
| sb.append(indentOld); | |
| sb.append('}'); | |
| sb.append(eol); | |
| // | |
| _insertBuilder(sb, statementsRange.length); | |
| // add proposal | |
| _addAssist(DartAssistKind.SURROUND_WITH_TRY_FINALLY, []); | |
| } | |
| } | |
| /** | |
| * Adds a new [Edit] to [edits]. | |
| */ | |
| void _addRemoveEdit(SourceRange range) { | |
| _addReplaceEdit(range, ''); | |
| } | |
| /** | |
| * Adds a new [SourceEdit] to [edits]. | |
| */ | |
| void _addReplaceEdit(SourceRange range, String text) { | |
| SourceEdit edit = new SourceEdit(range.offset, range.length, text); | |
| doSourceChange_addElementEdit(change, unitElement, edit); | |
| } | |
| /** | |
| * Configures [utils] using given [target]. | |
| */ | |
| void _configureTargetLocation(Object target) { | |
| utils.targetClassElement = null; | |
| if (target is AstNode) { | |
| ClassDeclaration targetClassDeclaration = | |
| target.getAncestor((node) => node is ClassDeclaration); | |
| if (targetClassDeclaration != null) { | |
| utils.targetClassElement = targetClassDeclaration.element; | |
| } | |
| } | |
| } | |
| /** | |
| * Returns an existing or just added [LinkedEditGroup] with [groupId]. | |
| */ | |
| LinkedEditGroup _getLinkedPosition(String groupId) { | |
| LinkedEditGroup group = linkedPositionGroups[groupId]; | |
| if (group == null) { | |
| group = new LinkedEditGroup.empty(); | |
| linkedPositionGroups[groupId] = group; | |
| } | |
| return group; | |
| } | |
| /** | |
| * Returns the text of the given node in the unit. | |
| */ | |
| String _getNodeText(AstNode node) { | |
| return utils.getNodeText(node); | |
| } | |
| /** | |
| * Returns the text of the given range in the unit. | |
| */ | |
| String _getRangeText(SourceRange range) { | |
| return utils.getRangeText(range); | |
| } | |
| /** | |
| * Inserts the given [SourceBuilder] at its offset. | |
| */ | |
| void _insertBuilder(SourceBuilder builder, [int length = 0]) { | |
| { | |
| SourceRange range = rangeStartLength(builder.offset, length); | |
| String text = builder.toString(); | |
| _addReplaceEdit(range, text); | |
| } | |
| // add linked positions | |
| builder.linkedPositionGroups.forEach((String id, LinkedEditGroup group) { | |
| LinkedEditGroup fixGroup = _getLinkedPosition(id); | |
| group.positions.forEach((Position position) { | |
| fixGroup.addPosition(position, group.length); | |
| }); | |
| group.suggestions.forEach((LinkedEditSuggestion suggestion) { | |
| fixGroup.addSuggestion(suggestion); | |
| }); | |
| }); | |
| // add exit position | |
| { | |
| int exitOffset = builder.exitOffset; | |
| if (exitOffset != null) { | |
| exitPosition = _newPosition(exitOffset); | |
| } | |
| } | |
| } | |
| Position _newPosition(int offset) { | |
| return new Position(file, offset); | |
| } | |
| /** | |
| * This method does nothing, but we invoke it in places where Dart VM | |
| * coverage agent fails to provide coverage information - such as almost | |
| * all "return" statements. | |
| * | |
| * https://code.google.com/p/dart/issues/detail?id=19912 | |
| */ | |
| static void _coverageMarker() {} | |
| /** | |
| * Returns `true` if the selection covers an operator of the given | |
| * [BinaryExpression]. | |
| */ | |
| static bool _isOperatorSelected( | |
| BinaryExpression binaryExpression, int offset, int length) { | |
| AstNode left = binaryExpression.leftOperand; | |
| AstNode right = binaryExpression.rightOperand; | |
| // between the nodes | |
| if (offset >= left.endToken.end && offset + length <= right.offset) { | |
| _coverageMarker(); | |
| return true; | |
| } | |
| // or exactly select the node (but not with infix expressions) | |
| if (offset == left.offset && offset + length == right.endToken.end) { | |
| if (left is BinaryExpression || right is BinaryExpression) { | |
| _coverageMarker(); | |
| return false; | |
| } | |
| _coverageMarker(); | |
| return true; | |
| } | |
| // invalid selection (part of node, etc) | |
| _coverageMarker(); | |
| return false; | |
| } | |
| /** | |
| * Checks if the given [Expression] should be wrapped with parenthesis when we | |
| * want to use it as operand of a logical `and` expression. | |
| */ | |
| static bool _shouldWrapParenthesisBeforeAnd(Expression expr) { | |
| if (expr is BinaryExpression) { | |
| BinaryExpression binary = expr; | |
| int precedence = binary.operator.type.precedence; | |
| return precedence < TokenClass.LOGICAL_AND_OPERATOR.precedence; | |
| } | |
| return false; | |
| } | |
| } | |
| /** | |
| * An [AssistContributor] that provides the default set of assists. | |
| */ | |
| class DefaultAssistContributor extends DartAssistContributor { | |
| @override | |
| Future<List<Assist>> internalComputeAssists(DartAssistContext context) { | |
| AssistProcessor processor = new AssistProcessor(context); | |
| return processor.compute(); | |
| } | |
| } | |
| class _SimpleIdentifierRecursiveAstVisitor extends RecursiveAstVisitor { | |
| final _SimpleIdentifierVisitor visitor; | |
| _SimpleIdentifierRecursiveAstVisitor(this.visitor); | |
| @override | |
| visitSimpleIdentifier(SimpleIdentifier node) { | |
| visitor(node); | |
| } | |
| } |