/* * 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.Iterables; import com.google.common.collect.Lists; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.CatchClause; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.DoStatement; import com.google.dart.engine.ast.ForStatement; import com.google.dart.engine.ast.SwitchMember; import com.google.dart.engine.ast.SwitchStatement; import com.google.dart.engine.ast.TryStatement; import com.google.dart.engine.ast.WhileStatement; import com.google.dart.engine.scanner.Token; import com.google.dart.engine.services.internal.util.TokenUtils; import com.google.dart.engine.services.status.RefactoringStatus; import com.google.dart.engine.services.status.RefactoringStatusContext; import com.google.dart.engine.services.util.SelectionAnalyzer; import com.google.dart.engine.utilities.source.SourceRange; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndEnd; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartStart; import java.util.List; /** * Analyzer to check if a selection covers a valid set of statements of AST. */ public class StatementAnalyzer extends SelectionAnalyzer { /** * @return <code>true</code> if "nodes" contains "node". */ private static boolean contains(List<AstNode> nodes, AstNode node) { return nodes.contains(node); } /** * @return <code>true</code> if "nodes" contains one of the "otherNodes". */ private static boolean contains(List<AstNode> nodes, List<? extends AstNode> otherNodes) { for (AstNode otherNode : otherNodes) { if (nodes.contains(otherNode)) { return true; } } return false; } protected final CorrectionUtils utils; private final RefactoringStatus status = new RefactoringStatus(); public StatementAnalyzer(CompilationUnit cunit, SourceRange selection) throws Exception { this(new CorrectionUtils(cunit), selection); } public StatementAnalyzer(CorrectionUtils utils, SourceRange selection) { super(selection); this.utils = utils; } /** * @return the {@link RefactoringStatus} result of checking selection. */ public RefactoringStatus getStatus() { return status; } @Override public Void visitCompilationUnit(CompilationUnit node) { super.visitCompilationUnit(node); if (!hasSelectedNodes()) { return null; } // check that selection does not begin/end in comment { int selectionStart = selection.getOffset(); int selectionEnd = selection.getEnd(); List<SourceRange> commentRanges = utils.getCommentRanges(); for (SourceRange commentRange : commentRanges) { if (commentRange.contains(selectionStart)) { invalidSelection("Selection begins inside a comment."); } if (commentRange.containsExclusive(selectionEnd)) { invalidSelection("Selection ends inside a comment."); } } } // more checks if (!status.hasFatalError()) { checkSelectedNodes(node); } return null; } @Override public Void visitDoStatement(DoStatement node) { super.visitDoStatement(node); List<AstNode> selectedNodes = getSelectedNodes(); if (contains(selectedNodes, node.getBody())) { invalidSelection("Operation not applicable to a 'do' statement's body and expression."); } return null; } @Override public Void visitForStatement(ForStatement node) { super.visitForStatement(node); List<AstNode> selectedNodes = getSelectedNodes(); boolean containsInit = contains(selectedNodes, node.getInitialization()) || contains(selectedNodes, node.getVariables()); boolean containsCondition = contains(selectedNodes, node.getCondition()); boolean containsUpdaters = contains(selectedNodes, node.getUpdaters()); boolean containsBody = contains(selectedNodes, node.getBody()); if (containsInit && containsCondition) { invalidSelection("Operation not applicable to a 'for' statement's initializer and condition."); } else if (containsCondition && containsUpdaters) { invalidSelection("Operation not applicable to a 'for' statement's condition and updaters."); } else if (containsUpdaters && containsBody) { invalidSelection("Operation not applicable to a 'for' statement's updaters and body."); } return null; } @Override public Void visitSwitchStatement(SwitchStatement node) { super.visitSwitchStatement(node); List<AstNode> selectedNodes = getSelectedNodes(); List<SwitchMember> switchMembers = node.getMembers(); for (AstNode selectedNode : selectedNodes) { if (switchMembers.contains(selectedNode)) { invalidSelection("Selection must either cover whole switch statement or parts of a single case block."); break; } } return null; } @Override public Void visitTryStatement(TryStatement node) { super.visitTryStatement(node); AstNode firstSelectedNode = getFirstSelectedNode(); if (firstSelectedNode != null) { if (firstSelectedNode == node.getBody() || firstSelectedNode == node.getFinallyBlock()) { invalidSelection("Selection must either cover whole try statement or parts of try, catch, or finally block."); } else { List<CatchClause> catchClauses = node.getCatchClauses(); for (CatchClause catchClause : catchClauses) { if (firstSelectedNode == catchClause || firstSelectedNode == catchClause.getBody() || firstSelectedNode == catchClause.getExceptionParameter()) { invalidSelection("Selection must either cover whole try statement or parts of try, catch, or finally block."); } } } } return null; } @Override public Void visitWhileStatement(WhileStatement node) { super.visitWhileStatement(node); List<AstNode> selectedNodes = getSelectedNodes(); if (contains(selectedNodes, node.getCondition()) && contains(selectedNodes, node.getBody())) { invalidSelection("Operation not applicable to a while statement's expression and body."); } return null; } /** * Records fatal error with given message. */ protected final void invalidSelection(String message) { invalidSelection(message, null); } /** * Records fatal error with given message and {@link RefactoringStatusContext}. */ protected final void invalidSelection(String message, RefactoringStatusContext context) { status.addFatalError(message, context); reset(); } /** * Checks final selected {@link AstNode}s after processing {@link CompilationUnit}. */ private void checkSelectedNodes(CompilationUnit unit) { List<AstNode> nodes = getSelectedNodes(); // some tokens before first selected node { AstNode firstNode = nodes.get(0); SourceRange rangeBeforeFirstNode = rangeStartStart(selection, firstNode); if (hasTokens(rangeBeforeFirstNode)) { invalidSelection( "The beginning of the selection contains characters that do not belong to a statement.", new RefactoringStatusContext(unit, rangeBeforeFirstNode)); } } // some tokens after last selected node { AstNode lastNode = Iterables.getLast(nodes); SourceRange rangeAfterLastNode = rangeEndEnd(lastNode, selection); if (hasTokens(rangeAfterLastNode)) { invalidSelection( "The end of the selection contains characters that do not belong to a statement.", new RefactoringStatusContext(unit, rangeAfterLastNode)); } } } /** * @return the {@link Token}s in given {@link SourceRange}. */ private List<Token> getTokens(SourceRange range) { try { String text = utils.getText(range); return TokenUtils.getTokens(text); } catch (Throwable e) { return Lists.newArrayList(); } } /** * @return <code>true</code> if there are {@link Token}s in the given {@link SourceRange}. */ private boolean hasTokens(SourceRange range) { return !getTokens(range).isEmpty(); } }