Too much stuff in one commit. Rename blocks now follows language semantics. Removal of more ASTPath consumers
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
31 * Microsystems, Inc. All Rights Reserved.
33 * If you wish your version of this file to be governed by only the CDDL
34 * or only the GPL Version 2, indicate your decision by adding
35 * "[Contributor] elects to include this software in this distribution
36 * under the [CDDL or GPL Version 2] license." If you do not indicate a
37 * single choice of license, a recipient has the option to distribute
38 * your version of this file under either the CDDL, the GPL Version 2 or
39 * to extend the choice of license to its licensees as provided above.
40 * However, if you add GPL Version 2 code and therefore, elected the GPL
41 * Version 2 license, then the option applies only if the new code is
42 * made subject to such option by the copyright holder.
44 package org.netbeans.modules.ruby;
46 import java.util.Collections;
47 import java.util.HashSet;
50 import org.jrubyparser.ast.ArgsNode;
51 import org.jrubyparser.ast.ArgumentNode;
52 import org.jrubyparser.ast.BlockArgNode;
53 import org.jrubyparser.ast.ListNode;
54 import org.jrubyparser.ast.LocalAsgnNode;
55 import org.jrubyparser.ast.LocalVarNode;
56 import org.jrubyparser.ast.MethodDefNode;
57 import org.jrubyparser.ast.Node;
58 import org.jrubyparser.ast.NodeType;
59 import org.jrubyparser.ast.INameNode;
60 import org.jrubyparser.SourcePosition;
61 import org.jrubyparser.ast.AssignableNode;
62 import org.jrubyparser.ast.ILocalScope;
63 import org.jrubyparser.ast.ILocalVariable;
64 import org.netbeans.modules.csl.api.InstantRenamer;
65 import org.netbeans.modules.csl.api.OffsetRange;
66 import org.netbeans.modules.csl.spi.ParserResult;
67 import org.netbeans.modules.ruby.lexer.LexUtilities;
68 import org.openide.util.NbBundle;
71 * Handle renaming of local elements
72 * @todo I should be able to rename top-level methods as well since they
74 * @todo Rename |j| in the following will only rename "j" inside the block!
85 * @todo When you fix, make sure BlockarReuse is also fixed!
86 * @todo Try renaming "hello" in the exception here; my code is confused
87 * about what I'm renaming (aliases method name) and the refactoring dialog
88 * name is wrong! This is happening because it's also changing GlobalAsgnNode for $!
89 * but its parent is LocalAsgnNode, and -its- -grand- parent is a RescueBodyNode!
90 * I should special case this!
97 rescue Exception => hello
106 public class RubyRenameHandler implements InstantRenamer {
108 public RubyRenameHandler() {
112 public boolean isRenameAllowed(ParserResult info, int caretOffset, String[] explanationRetValue) {
113 Node root = AstUtilities.getRoot(info);
116 explanationRetValue[0] = NbBundle.getMessage(RubyRenameHandler.class, "NoRenameWithErrors");
120 Node closest = root.getNodeAt(AstUtilities.getAstOffset(info, caretOffset));
121 if (closest == null) return false;
122 if (closest instanceof ILocalVariable) return true; // All local block/method vars can be renamed
124 switch (closest.getNodeType()) {
125 case INSTASGNNODE: case INSTVARNODE: case CLASSVARDECLNODE: case CLASSVARNODE: case CLASSVARASGNNODE:
126 case GLOBALASGNNODE: case GLOBALVARNODE: case CONSTDECLNODE: case CONSTNODE: case DEFNNODE: case DEFSNODE:
127 case FCALLNODE: case CALLNODE: case VCALLNODE: case COLON2NODE: case COLON3NODE: case ALIASNODE:
128 case SYMBOLNODE: // TODO - what about the string arguments in an alias node? Gotta check those
136 public Set<OffsetRange> getRenameRegions(ParserResult info, int caretOffset) {
137 Node root = AstUtilities.getRoot(info);
138 if (root == null) return Collections.emptySet();
140 Set<OffsetRange> regions = new HashSet<OffsetRange>();
142 int astOffset = AstUtilities.getAstOffset(info, caretOffset);
143 if (astOffset == -1) return Collections.emptySet();
145 AstPath path = new AstPath(root, astOffset);
146 Node closest = path.leaf();
147 if (closest == null) return Collections.emptySet();
149 if (closest.isBlockParameter()) {
150 ILocalVariable blockParameter = (ILocalVariable) closest;
152 for (ILocalVariable occurrence: blockParameter.getOccurrences()) {
153 OffsetRange range = LexUtilities.getLexerOffsets(info,
154 AstUtilities.offsetRangeFor(occurrence.getLexicalNamePosition()));
156 if (range != OffsetRange.NONE) regions.add(range);
158 } else if (closest instanceof LocalVarNode || closest instanceof LocalAsgnNode) {
159 // A local variable read or a parameter read, or an assignment to one of these
160 String name = ((INameNode)closest).getName();
161 Node localScope = AstUtilities.findLocalScope(closest, path);
163 if (localScope == null) {
164 // Use parent, possibly Grand Parent if we have a newline node in the way
165 localScope = path.leafParent();
167 if (localScope.getNodeType() == NodeType.NEWLINENODE) localScope = path.leafGrandParent();
168 if (localScope == null) localScope = closest;
171 addLocals(info, localScope, name, regions);
172 } else if (closest.getNodeType() == NodeType.DVARNODE || closest.getNodeType() == NodeType.DASGNNODE) {
173 // 1.8-style block declaration: iter { dasgn }
174 String name = ((INameNode)closest).getName();
176 for (Node block : AstUtilities.getApplicableBlocks(path, true)) {
177 addDynamicVars(info, block, name, regions);
179 } else if (closest.getNodeType() == NodeType.ARGUMENTNODE || closest.getNodeType() == NodeType.BLOCKARGNODE) {
180 // A method name (if under a DefnNode or DefsNode) or a parameter (if indirectly under an ArgsNode)
181 String name = ((INameNode)closest).getName();
183 Node parent = path.leafParent();
185 if (parent != null) {
186 // Make sure it's a parameter, not a method
187 if (!(parent instanceof MethodDefNode)) {
188 // Parameter (check to see if its under ArgumentNode)
189 Node method = AstUtilities.findMethodAtOffset(root, astOffset);
191 if (method == null) method = AstUtilities.findBlock(path);
193 if (method == null) {
194 // Use parent, possibly Grand Parent if we have a newline node in the way
195 method = path.leafParent();
197 if (method.getNodeType() == NodeType.NEWLINENODE) method = path.leafGrandParent();
198 if (method == null) method = closest;
201 addLocals(info, method, name, regions);
209 private void addLocals(ParserResult info, Node node, String name, Set<OffsetRange> ranges) {
210 if (node.getNodeType() == NodeType.LOCALVARNODE) {
211 if (((INameNode)node).getName().equals(name)) {
212 OffsetRange range = AstUtilities.getRange(node);
213 range = LexUtilities.getLexerOffsets(info, range);
214 if (range != OffsetRange.NONE) ranges.add(range);
216 } else if (node.getNodeType() == NodeType.LOCALASGNNODE) {
217 if (((INameNode)node).getName().equals(name)) {
218 OffsetRange range = AstUtilities.getRange(node);
219 // Adjust end offset to only include the left hand size
220 range = new OffsetRange(range.getStart(), range.getStart() + name.length());
221 range = LexUtilities.getLexerOffsets(info, range);
222 if (range != OffsetRange.NONE) ranges.add(range);
224 } else if (node.getNodeType() == NodeType.ARGSNODE) {
225 addArgsNode(node, name, info, ranges);
228 for (Node child : node.childNodes()) {
229 addLocals(info, child, name, ranges);
234 // quick tip renaming
236 // occurrences marking
238 // live code templates
239 // ...anyone else who calls findBlock
241 // Test both parent blocks, sibling blocks and descendant blocks
242 // Make sure the "isUsed" detection is smarter too.
244 private void addDynamicVars(ParserResult info, Node node, String name, Set<OffsetRange> ranges) {
245 switch (node.getNodeType()) {
247 if (((INameNode)node).getName().equals(name)) {
248 OffsetRange range = LexUtilities.getLexerOffsets(info, AstUtilities.getRange(node));
249 if (range != OffsetRange.NONE) ranges.add(range);
253 if (((INameNode)node).getName().equals(name)) {
254 OffsetRange range = AstUtilities.offsetRangeFor(((AssignableNode) node).getLeftHandSidePosition());
255 range = LexUtilities.getLexerOffsets(info, range);
256 if (range != OffsetRange.NONE) ranges.add(range);
260 addArgsNode(node, name, info, ranges);
263 for (Node child : node.childNodes()) {
264 if (child instanceof ILocalScope || child.getNodeType() == NodeType.ITERNODE) continue;
266 addDynamicVars(info, child, name, ranges);
270 private void addArgsNode(Node node, String name, ParserResult info, Set<OffsetRange> ranges) {
271 ArgsNode an = (ArgsNode)node;
273 if (an.getRequiredCount() > 0) {
274 for (Node arg : an.childNodes()) {
275 if (!(arg instanceof ListNode)) continue;
276 for (Node arg2 : arg.childNodes()) {
277 if (arg2.getNodeType() == NodeType.ARGUMENTNODE) {
278 if (((ArgumentNode)arg2).getName().equals(name)) {
279 OffsetRange range = AstUtilities.getRange(arg2);
280 range = LexUtilities.getLexerOffsets(info, range);
281 if (range != OffsetRange.NONE) ranges.add(range);
283 } else if (arg2.getNodeType() == NodeType.LOCALASGNNODE) {
284 if (((LocalAsgnNode)arg2).getName().equals(name)) {
285 OffsetRange range = AstUtilities.getRange(arg2);
286 // Adjust end offset to only include the left hand size
287 range = new OffsetRange(range.getStart(), range.getStart() + name.length());
288 range = LexUtilities.getLexerOffsets(info, range);
289 if (range != OffsetRange.NONE) ranges.add(range);
297 if (an.getRest() != null) {
298 ArgumentNode bn = an.getRest();
300 if (bn.getName().equals(name)) {
301 SourcePosition pos = bn.getPosition();
303 // +1: Skip "*" and "&" prefix
304 OffsetRange range = new OffsetRange(pos.getStartOffset() + 1, pos.getEndOffset());
305 range = LexUtilities.getLexerOffsets(info, range);
306 if (range != OffsetRange.NONE) ranges.add(range);
310 if (an.getBlock() != null) {
311 BlockArgNode bn = an.getBlock();
313 if (bn.getName().equals(name)) {
314 SourcePosition pos = bn.getPosition();
316 // +1: Skip "*" and "&" prefix
317 OffsetRange range = new OffsetRange(pos.getStartOffset() + 1, pos.getEndOffset());
318 range = LexUtilities.getLexerOffsets(info, range);
319 if (range != OffsetRange.NONE) ranges.add(range);